From d7b2277d2bf6c500d48c89e4e96d9364fb6d0272 Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Mon, 30 Mar 2026 07:30:15 +0800 Subject: [PATCH] Re-translate the Japanese version (#1871) * Retranslate Japanese docs with GPT-5.4 * Retranslate Japanese code with GPT-5.4 --- ja/CONTRIBUTING.md | 134 -- ja/README.md | 44 +- ja/codes/c/.gitignore | 9 + ja/codes/c/CMakeLists.txt | 20 + .../CMakeLists.txt | 3 + .../c/chapter_array_and_linkedlist/array.c | 114 ++ .../linked_list.c | 89 ++ .../c/chapter_array_and_linkedlist/my_list.c | 163 +++ .../c/chapter_backtracking/CMakeLists.txt | 10 + ja/codes/c/chapter_backtracking/n_queens.c | 95 ++ .../c/chapter_backtracking/permutations_i.c | 79 ++ .../c/chapter_backtracking/permutations_ii.c | 81 ++ .../preorder_traversal_i_compact.c | 49 + .../preorder_traversal_ii_compact.c | 61 + .../preorder_traversal_iii_compact.c | 62 + .../preorder_traversal_iii_template.c | 93 ++ .../c/chapter_backtracking/subset_sum_i.c | 78 ++ .../chapter_backtracking/subset_sum_i_naive.c | 69 + .../c/chapter_backtracking/subset_sum_ii.c | 83 ++ .../CMakeLists.txt | 5 + .../iteration.c | 81 ++ .../recursion.c | 77 ++ .../space_complexity.c | 141 ++ .../time_complexity.c | 179 +++ .../worst_best_time_complexity.c | 57 + .../chapter_divide_and_conquer/CMakeLists.txt | 3 + .../binary_search_recur.c | 47 + .../c/chapter_divide_and_conquer/build_tree.c | 61 + .../c/chapter_divide_and_conquer/hanota.c | 74 ++ .../CMakeLists.txt | 8 + .../climbing_stairs_backtrack.c | 47 + .../climbing_stairs_constraint_dp.c | 46 + .../climbing_stairs_dfs.c | 32 + .../climbing_stairs_dfs_mem.c | 44 + .../climbing_stairs_dp.c | 51 + .../chapter_dynamic_programming/coin_change.c | 92 ++ .../coin_change_ii.c | 81 ++ .../edit_distance.c | 159 +++ .../c/chapter_dynamic_programming/knapsack.c | 137 ++ .../min_cost_climbing_stairs_dp.c | 62 + .../min_path_sum.c | 134 ++ .../unbounded_knapsack.c | 81 ++ ja/codes/c/chapter_graph/CMakeLists.txt | 4 + .../c/chapter_graph/graph_adjacency_list.c | 171 +++ .../chapter_graph/graph_adjacency_list_test.c | 55 + .../c/chapter_graph/graph_adjacency_matrix.c | 150 +++ ja/codes/c/chapter_graph/graph_bfs.c | 116 ++ ja/codes/c/chapter_graph/graph_dfs.c | 75 ++ ja/codes/c/chapter_greedy/CMakeLists.txt | 8 + .../c/chapter_greedy/coin_change_greedy.c | 60 + .../c/chapter_greedy/fractional_knapsack.c | 60 + ja/codes/c/chapter_greedy/max_capacity.c | 49 + .../c/chapter_greedy/max_product_cutting.c | 38 + ja/codes/c/chapter_hashing/CMakeLists.txt | 4 + ja/codes/c/chapter_hashing/array_hash_map.c | 215 ++++ .../c/chapter_hashing/hash_map_chaining.c | 213 +++ .../hash_map_open_addressing.c | 211 +++ ja/codes/c/chapter_hashing/simple_hash.c | 68 + ja/codes/c/chapter_heap/CMakeLists.txt | 2 + ja/codes/c/chapter_heap/my_heap.c | 152 +++ ja/codes/c/chapter_heap/my_heap_test.c | 41 + ja/codes/c/chapter_heap/top_k.c | 73 ++ ja/codes/c/chapter_searching/CMakeLists.txt | 4 + ja/codes/c/chapter_searching/binary_search.c | 59 + .../c/chapter_searching/binary_search_edge.c | 67 + .../binary_search_insertion.c | 68 + ja/codes/c/chapter_searching/two_sum.c | 86 ++ ja/codes/c/chapter_sorting/CMakeLists.txt | 9 + ja/codes/c/chapter_sorting/bubble_sort.c | 61 + ja/codes/c/chapter_sorting/bucket_sort.c | 57 + ja/codes/c/chapter_sorting/counting_sort.c | 87 ++ ja/codes/c/chapter_sorting/heap_sort.c | 60 + ja/codes/c/chapter_sorting/insertion_sort.c | 36 + ja/codes/c/chapter_sorting/merge_sort.c | 63 + ja/codes/c/chapter_sorting/quick_sort.c | 137 ++ ja/codes/c/chapter_sorting/radix_sort.c | 75 ++ ja/codes/c/chapter_sorting/selection_sort.c | 37 + .../c/chapter_stack_and_queue/CMakeLists.txt | 6 + .../c/chapter_stack_and_queue/array_deque.c | 172 +++ .../c/chapter_stack_and_queue/array_queue.c | 134 ++ .../c/chapter_stack_and_queue/array_stack.c | 103 ++ .../linkedlist_deque.c | 212 +++ .../linkedlist_queue.c | 128 ++ .../linkedlist_stack.c | 107 ++ ja/codes/c/chapter_tree/CMakeLists.txt | 6 + ja/codes/c/chapter_tree/array_binary_tree.c | 166 +++ ja/codes/c/chapter_tree/avl_tree.c | 259 ++++ ja/codes/c/chapter_tree/binary_search_tree.c | 171 +++ ja/codes/c/chapter_tree/binary_tree.c | 43 + ja/codes/c/chapter_tree/binary_tree_bfs.c | 73 ++ ja/codes/c/chapter_tree/binary_tree_dfs.c | 75 ++ ja/codes/c/utils/CMakeLists.txt | 5 + ja/codes/c/utils/common.h | 36 + ja/codes/c/utils/common_test.c | 35 + ja/codes/c/utils/list_node.h | 59 + ja/codes/c/utils/print_util.h | 131 ++ ja/codes/c/utils/tree_node.h | 107 ++ ja/codes/c/utils/uthash.h | 1140 +++++++++++++++++ ja/codes/c/utils/vector.h | 259 ++++ ja/codes/c/utils/vertex.h | 49 + ja/codes/cpp/.gitignore | 10 + ja/codes/cpp/CMakeLists.txt | 20 + .../CMakeLists.txt | 4 + .../chapter_array_and_linkedlist/array.cpp | 62 +- .../linked_list.cpp | 30 +- .../cpp/chapter_array_and_linkedlist/list.cpp | 30 +- .../chapter_array_and_linkedlist/my_list.cpp | 68 +- .../cpp/chapter_backtracking/CMakeLists.txt | 10 + .../cpp/chapter_backtracking/n_queens.cpp | 32 +- .../chapter_backtracking/permutations_i.cpp | 18 +- .../chapter_backtracking/permutations_ii.cpp | 20 +- .../preorder_traversal_i_compact.cpp | 16 +- .../preorder_traversal_ii_compact.cpp | 21 +- .../preorder_traversal_iii_compact.cpp | 23 +- .../preorder_traversal_iii_template.cpp | 29 +- .../cpp/chapter_backtracking/subset_sum_i.cpp | 30 +- .../subset_sum_i_naive.cpp | 29 +- .../chapter_backtracking/subset_sum_ii.cpp | 34 +- .../CMakeLists.txt | 5 + .../iteration.cpp | 30 +- .../recursion.cpp | 22 +- .../space_complexity.cpp | 44 +- .../time_complexity.cpp | 68 +- .../worst_best_time_complexity.cpp | 18 +- .../chapter_divide_and_conquer/CMakeLists.txt | 3 + .../binary_search_recur.cpp | 16 +- .../chapter_divide_and_conquer/build_tree.cpp | 26 +- .../cpp/chapter_divide_and_conquer/hanota.cpp | 30 +- .../CMakeLists.txt | 10 + .../climbing_stairs_backtrack.cpp | 21 +- .../climbing_stairs_constraint_dp.cpp | 14 +- .../climbing_stairs_dfs.cpp | 10 +- .../climbing_stairs_dfs_mem.cpp | 14 +- .../climbing_stairs_dp.cpp | 18 +- .../coin_change.cpp | 32 +- .../coin_change_ii.cpp | 30 +- .../edit_distance.cpp | 100 +- .../chapter_dynamic_programming/knapsack.cpp | 79 +- .../min_cost_climbing_stairs_dp.cpp | 26 +- .../min_path_sum.cpp | 94 +- .../unbounded_knapsack.cpp | 28 +- ja/codes/cpp/chapter_graph/CMakeLists.txt | 5 + .../chapter_graph/graph_adjacency_list.cpp | 20 +- .../graph_adjacency_list_test.cpp | 49 + .../chapter_graph/graph_adjacency_matrix.cpp | 56 +- ja/codes/cpp/chapter_graph/graph_bfs.cpp | 38 +- ja/codes/cpp/chapter_graph/graph_dfs.cpp | 28 +- ja/codes/cpp/chapter_greedy/CMakeLists.txt | 3 + .../cpp/chapter_greedy/coin_change_greedy.cpp | 32 +- .../chapter_greedy/fractional_knapsack.cpp | 26 +- ja/codes/cpp/chapter_greedy/max_capacity.cpp | 18 +- .../chapter_greedy/max_product_cutting.cpp | 20 +- ja/codes/cpp/chapter_hashing/CMakeLists.txt | 6 + .../cpp/chapter_hashing/array_hash_map.cpp | 18 +- .../chapter_hashing/array_hash_map_test.cpp | 34 +- .../cpp/chapter_hashing/built_in_hash.cpp | 18 +- ja/codes/cpp/chapter_hashing/hash_map.cpp | 32 +- .../cpp/chapter_hashing/hash_map_chaining.cpp | 64 +- .../hash_map_open_addressing.cpp | 82 +- ja/codes/cpp/chapter_hashing/simple_hash.cpp | 16 +- ja/codes/cpp/chapter_heap/CMakeLists.txt | 3 + ja/codes/cpp/chapter_heap/heap.cpp | 34 +- ja/codes/cpp/chapter_heap/my_heap.cpp | 76 +- ja/codes/cpp/chapter_heap/top_k.cpp | 14 +- ja/codes/cpp/chapter_searching/CMakeLists.txt | 4 + .../cpp/chapter_searching/binary_search.cpp | 40 +- .../chapter_searching/binary_search_edge.cpp | 40 +- .../binary_search_insertion.cpp | 40 +- .../cpp/chapter_searching/hashing_search.cpp | 20 +- .../cpp/chapter_searching/linear_search.cpp | 22 +- ja/codes/cpp/chapter_searching/two_sum.cpp | 26 +- ja/codes/cpp/chapter_sorting/CMakeLists.txt | 6 + ja/codes/cpp/chapter_sorting/bubble_sort.cpp | 32 +- ja/codes/cpp/chapter_sorting/bucket_sort.cpp | 22 +- .../cpp/chapter_sorting/counting_sort.cpp | 44 +- ja/codes/cpp/chapter_sorting/heap_sort.cpp | 24 +- .../cpp/chapter_sorting/insertion_sort.cpp | 14 +- ja/codes/cpp/chapter_sorting/merge_sort.cpp | 30 +- ja/codes/cpp/chapter_sorting/quick_sort.cpp | 119 +- ja/codes/cpp/chapter_sorting/radix_sort.cpp | 40 +- .../cpp/chapter_sorting/selection_sort.cpp | 12 +- .../chapter_stack_and_queue/CMakeLists.txt | 9 + .../chapter_stack_and_queue/array_deque.cpp | 72 +- .../chapter_stack_and_queue/array_queue.cpp | 48 +- .../chapter_stack_and_queue/array_stack.cpp | 30 +- .../cpp/chapter_stack_and_queue/deque.cpp | 22 +- .../linkedlist_deque.cpp | 88 +- .../linkedlist_queue.cpp | 42 +- .../linkedlist_stack.cpp | 36 +- .../cpp/chapter_stack_and_queue/queue.cpp | 20 +- .../cpp/chapter_stack_and_queue/stack.cpp | 20 +- ja/codes/cpp/chapter_tree/CMakeLists.txt | 6 + .../cpp/chapter_tree/array_binary_tree.cpp | 52 +- ja/codes/cpp/chapter_tree/avl_tree.cpp | 112 +- .../cpp/chapter_tree/binary_search_tree.cpp | 76 +- ja/codes/cpp/chapter_tree/binary_tree.cpp | 18 +- ja/codes/cpp/chapter_tree/binary_tree_bfs.cpp | 20 +- ja/codes/cpp/chapter_tree/binary_tree_dfs.cpp | 24 +- ja/codes/cpp/utils/CMakeLists.txt | 4 + ja/codes/cpp/utils/common.hpp | 2 +- ja/codes/cpp/utils/list_node.hpp | 8 +- ja/codes/cpp/utils/print_utils.hpp | 50 +- ja/codes/cpp/utils/tree_node.hpp | 38 +- ja/codes/cpp/utils/vertex.hpp | 6 +- ja/codes/csharp/.editorconfig | 88 ++ ja/codes/csharp/.gitignore | 5 + ja/codes/csharp/GlobalUsing.cs | 3 + .../chapter_array_and_linkedlist/array.cs | 107 ++ .../linked_list.cs | 80 ++ .../chapter_array_and_linkedlist/list.cs | 66 + .../chapter_array_and_linkedlist/my_list.cs | 144 +++ .../csharp/chapter_backtracking/n_queens.cs | 76 ++ .../chapter_backtracking/permutations_i.cs | 53 + .../chapter_backtracking/permutations_ii.cs | 55 + .../preorder_traversal_i_compact.cs | 37 + .../preorder_traversal_ii_compact.cs | 44 + .../preorder_traversal_iii_compact.cs | 45 + .../preorder_traversal_iii_template.cs | 72 ++ .../chapter_backtracking/subset_sum_i.cs | 55 + .../subset_sum_i_naive.cs | 53 + .../chapter_backtracking/subset_sum_ii.cs | 60 + .../iteration.cs | 77 ++ .../recursion.cs | 78 ++ .../space_complexity.cs | 104 ++ .../time_complexity.cs | 195 +++ .../worst_best_time_complexity.cs | 49 + .../binary_search_recur.cs | 46 + .../chapter_divide_and_conquer/build_tree.cs | 49 + .../chapter_divide_and_conquer/hanota.cs | 59 + .../climbing_stairs_backtrack.cs | 41 + .../climbing_stairs_constraint_dp.cs | 36 + .../climbing_stairs_dfs.cs | 31 + .../climbing_stairs_dfs_mem.cs | 39 + .../climbing_stairs_dp.cs | 49 + .../coin_change.cs | 71 + .../coin_change_ii.cs | 68 + .../edit_distance.cs | 141 ++ .../chapter_dynamic_programming/knapsack.cs | 118 ++ .../min_cost_climbing_stairs_dp.cs | 53 + .../min_path_sum.cs | 127 ++ .../unbounded_knapsack.cs | 64 + .../chapter_graph/graph_adjacency_list.cs | 122 ++ .../chapter_graph/graph_adjacency_matrix.cs | 137 ++ ja/codes/csharp/chapter_graph/graph_bfs.cs | 58 + ja/codes/csharp/chapter_graph/graph_dfs.cs | 54 + .../chapter_greedy/coin_change_greedy.cs | 54 + .../chapter_greedy/fractional_knapsack.cs | 52 + .../csharp/chapter_greedy/max_capacity.cs | 39 + .../chapter_greedy/max_product_cutting.cs | 39 + .../csharp/chapter_hashing/array_hash_map.cs | 134 ++ .../csharp/chapter_hashing/built_in_hash.cs | 36 + ja/codes/csharp/chapter_hashing/hash_map.cs | 51 + .../chapter_hashing/hash_map_chaining.cs | 144 +++ .../hash_map_open_addressing.cs | 159 +++ .../csharp/chapter_hashing/simple_hash.cs | 66 + ja/codes/csharp/chapter_heap/heap.cs | 64 + ja/codes/csharp/chapter_heap/my_heap.cs | 160 +++ ja/codes/csharp/chapter_heap/top_k.cs | 37 + .../csharp/chapter_searching/binary_search.cs | 59 + .../chapter_searching/binary_search_edge.cs | 50 + .../binary_search_insertion.cs | 64 + .../chapter_searching/hashing_search.cs | 50 + .../csharp/chapter_searching/linear_search.cs | 49 + ja/codes/csharp/chapter_searching/two_sum.cs | 52 + .../csharp/chapter_sorting/bubble_sort.cs | 51 + .../csharp/chapter_sorting/bucket_sort.cs | 46 + .../csharp/chapter_sorting/counting_sort.cs | 77 ++ ja/codes/csharp/chapter_sorting/heap_sort.cs | 52 + .../csharp/chapter_sorting/insertion_sort.cs | 30 + ja/codes/csharp/chapter_sorting/merge_sort.cs | 56 + ja/codes/csharp/chapter_sorting/quick_sort.cs | 150 +++ ja/codes/csharp/chapter_sorting/radix_sort.cs | 69 + .../csharp/chapter_sorting/selection_sort.cs | 32 + .../chapter_stack_and_queue/array_deque.cs | 152 +++ .../chapter_stack_and_queue/array_queue.cs | 114 ++ .../chapter_stack_and_queue/array_stack.cs | 84 ++ .../csharp/chapter_stack_and_queue/deque.cs | 44 + .../linkedlist_deque.cs | 177 +++ .../linkedlist_queue.cs | 106 ++ .../linkedlist_stack.cs | 97 ++ .../csharp/chapter_stack_and_queue/queue.cs | 39 + .../csharp/chapter_stack_and_queue/stack.cs | 40 + .../csharp/chapter_tree/array_binary_tree.cs | 129 ++ ja/codes/csharp/chapter_tree/avl_tree.cs | 216 ++++ .../csharp/chapter_tree/binary_search_tree.cs | 160 +++ ja/codes/csharp/chapter_tree/binary_tree.cs | 39 + .../csharp/chapter_tree/binary_tree_bfs.cs | 40 + .../csharp/chapter_tree/binary_tree_dfs.cs | 59 + ja/codes/csharp/csharp.sln | 25 + ja/codes/csharp/hello-algo.csproj | 21 + ja/codes/csharp/utils/ListNode.cs | 32 + ja/codes/csharp/utils/PrintUtil.cs | 132 ++ ja/codes/csharp/utils/TreeNode.cs | 67 + ja/codes/csharp/utils/Vertex.cs | 30 + ja/codes/dart/build.dart | 39 + .../chapter_array_and_linkedlist/array.dart | 105 ++ .../linked_list.dart | 83 ++ .../chapter_array_and_linkedlist/list.dart | 62 + .../chapter_array_and_linkedlist/my_list.dart | 132 ++ .../dart/chapter_backtracking/n_queens.dart | 75 ++ .../chapter_backtracking/permutations_i.dart | 51 + .../chapter_backtracking/permutations_ii.dart | 53 + .../preorder_traversal_i_compact.dart | 35 + .../preorder_traversal_ii_compact.dart | 47 + .../preorder_traversal_iii_compact.dart | 47 + .../preorder_traversal_iii_template.dart | 73 ++ .../chapter_backtracking/subset_sum_i.dart | 56 + .../subset_sum_i_naive.dart | 54 + .../chapter_backtracking/subset_sum_ii.dart | 61 + .../iteration.dart | 72 ++ .../recursion.dart | 70 + .../space_complexity.dart | 106 ++ .../time_complexity.dart | 165 +++ .../worst_best_time_complexity.dart | 40 + .../binary_search_recur.dart | 42 + .../build_tree.dart | 55 + .../chapter_divide_and_conquer/hanota.dart | 54 + .../climbing_stairs_backtrack.dart | 39 + .../climbing_stairs_constraint_dp.dart | 33 + .../climbing_stairs_dfs.dart | 27 + .../climbing_stairs_dfs_mem.dart | 33 + .../climbing_stairs_dp.dart | 43 + .../coin_change.dart | 68 + .../coin_change_ii.dart | 64 + .../edit_distance.dart | 125 ++ .../chapter_dynamic_programming/knapsack.dart | 116 ++ .../min_cost_climbing_stairs_dp.dart | 48 + .../min_path_sum.dart | 120 ++ .../unbounded_knapsack.dart | 62 + .../chapter_graph/graph_adjacency_list.dart | 124 ++ .../chapter_graph/graph_adjacency_matrix.dart | 133 ++ ja/codes/dart/chapter_graph/graph_bfs.dart | 66 + ja/codes/dart/chapter_graph/graph_dfs.dart | 59 + .../chapter_greedy/coin_change_greedy.dart | 50 + .../chapter_greedy/fractional_knapsack.dart | 47 + .../dart/chapter_greedy/max_capacity.dart | 37 + .../chapter_greedy/max_product_cutting.dart | 37 + .../dart/chapter_hashing/array_hash_map.dart | 126 ++ .../dart/chapter_hashing/built_in_hash.dart | 34 + ja/codes/dart/chapter_hashing/hash_map.dart | 41 + .../chapter_hashing/hash_map_chaining.dart | 138 ++ .../hash_map_open_addressing.dart | 157 +++ .../dart/chapter_hashing/simple_hash.dart | 62 + ja/codes/dart/chapter_heap/my_heap.dart | 151 +++ ja/codes/dart/chapter_heap/top_k.dart | 150 +++ .../dart/chapter_searching/binary_search.dart | 63 + .../chapter_searching/binary_search_edge.dart | 48 + .../binary_search_insertion.dart | 60 + .../chapter_searching/hashing_search.dart | 54 + .../dart/chapter_searching/linear_search.dart | 47 + ja/codes/dart/chapter_searching/two_sum.dart | 49 + .../dart/chapter_sorting/bubble_sort.dart | 51 + .../dart/chapter_sorting/bucket_sort.dart | 39 + .../dart/chapter_sorting/counting_sort.dart | 72 ++ ja/codes/dart/chapter_sorting/heap_sort.dart | 49 + .../dart/chapter_sorting/insertion_sort.dart | 26 + ja/codes/dart/chapter_sorting/merge_sort.dart | 52 + ja/codes/dart/chapter_sorting/quick_sort.dart | 145 +++ ja/codes/dart/chapter_sorting/radix_sort.dart | 71 + .../dart/chapter_sorting/selection_sort.dart | 29 + .../chapter_stack_and_queue/array_deque.dart | 146 +++ .../chapter_stack_and_queue/array_queue.dart | 110 ++ .../chapter_stack_and_queue/array_stack.dart | 77 ++ .../dart/chapter_stack_and_queue/deque.dart | 42 + .../linkedlist_deque.dart | 167 +++ .../linkedlist_queue.dart | 103 ++ .../linkedlist_stack.dart | 93 ++ .../dart/chapter_stack_and_queue/queue.dart | 37 + .../dart/chapter_stack_and_queue/stack.dart | 35 + .../dart/chapter_tree/array_binary_tree.dart | 152 +++ ja/codes/dart/chapter_tree/avl_tree.dart | 218 ++++ .../dart/chapter_tree/binary_search_tree.dart | 153 +++ ja/codes/dart/chapter_tree/binary_tree.dart | 37 + .../dart/chapter_tree/binary_tree_bfs.dart | 38 + .../dart/chapter_tree/binary_tree_dfs.dart | 62 + ja/codes/dart/utils/list_node.dart | 24 + ja/codes/dart/utils/print_util.dart | 90 ++ ja/codes/dart/utils/tree_node.dart | 50 + ja/codes/dart/utils/vertex.dart | 29 + .../go/chapter_array_and_linkedlist/array.go | 79 ++ .../array_test.go | 50 + .../linked_list.go | 51 + .../linked_list_test.go | 48 + .../chapter_array_and_linkedlist/list_test.go | 66 + .../chapter_array_and_linkedlist/my_list.go | 109 ++ .../my_list_test.go | 46 + ja/codes/go/chapter_backtracking/n_queens.go | 57 + .../go/chapter_backtracking/n_queens_test.go | 24 + .../chapter_backtracking/permutation_test.go | 33 + .../go/chapter_backtracking/permutations_i.go | 38 + .../chapter_backtracking/permutations_ii.go | 41 + .../preorder_traversal_i_compact.go | 22 + .../preorder_traversal_ii_compact.go | 26 + .../preorder_traversal_iii_compact.go | 27 + .../preorder_traversal_iii_template.go | 57 + .../preorder_traversal_test.go | 91 ++ .../go/chapter_backtracking/subset_sum_i.go | 42 + .../subset_sum_i_naive.go | 37 + .../go/chapter_backtracking/subset_sum_ii.go | 47 + .../chapter_backtracking/subset_sum_test.go | 56 + .../iteration.go | 59 + .../iteration_test.go | 26 + .../recursion.go | 61 + .../recursion_test.go | 26 + .../space_complexity.go | 106 ++ .../space_complexity_test.go | 26 + .../time_complexity.go | 130 ++ .../time_complexity_test.go | 48 + .../worst_best_time_complexity.go | 35 + .../worst_best_time_complexity_test.go | 20 + .../binary_search_recur.go | 34 + .../binary_search_recur_test.go | 20 + .../chapter_divide_and_conquer/build_tree.go | 37 + .../build_tree_test.go | 25 + .../go/chapter_divide_and_conquer/hanota.go | 39 + .../chapter_divide_and_conquer/hanota_test.go | 40 + .../climbing_stairs_backtrack.go | 36 + .../climbing_stairs_constraint_dp.go | 25 + .../climbing_stairs_dfs.go | 21 + .../climbing_stairs_dfs_mem.go | 32 + .../climbing_stairs_dp.go | 35 + .../climbing_stairs_test.go | 57 + .../coin_change.go | 66 + .../coin_change_ii.go | 54 + .../coin_change_test.go | 23 + .../edit_distance.go | 129 ++ .../edit_distance_test.go | 40 + .../chapter_dynamic_programming/knapsack.go | 87 ++ .../knapsack_test.go | 54 + .../min_cost_climbing_stairs_dp.go | 52 + .../min_path_sum.go | 94 ++ .../min_path_sum_test.go | 43 + .../unbounded_knapsack.go | 50 + .../go/chapter_graph/graph_adjacency_list.go | 100 ++ .../graph_adjacency_list_test.go | 45 + .../chapter_graph/graph_adjacency_matrix.go | 102 ++ .../graph_adjacency_matrix_test.go | 43 + ja/codes/go/chapter_graph/graph_bfs.go | 41 + ja/codes/go/chapter_graph/graph_bfs_test.go | 29 + ja/codes/go/chapter_graph/graph_dfs.go | 36 + ja/codes/go/chapter_graph/graph_dfs_test.go | 28 + .../go/chapter_greedy/coin_change_greedy.go | 27 + .../chapter_greedy/coin_change_greedy_test.go | 35 + .../go/chapter_greedy/fractional_knapsack.go | 41 + .../fractional_knapsack_test.go | 20 + ja/codes/go/chapter_greedy/max_capacity.go | 28 + .../go/chapter_greedy/max_capacity_test.go | 18 + .../go/chapter_greedy/max_product_cutting.go | 28 + .../max_product_cutting_test.go | 17 + ja/codes/go/chapter_hashing/array_hash_map.go | 97 ++ .../go/chapter_hashing/array_hash_map_test.go | 52 + .../go/chapter_hashing/hash_collision_test.go | 62 + .../go/chapter_hashing/hash_map_chaining.go | 134 ++ .../hash_map_open_addressing.go | 126 ++ ja/codes/go/chapter_hashing/hash_map_test.go | 74 ++ ja/codes/go/chapter_hashing/simple_hash.go | 55 + ja/codes/go/chapter_heap/heap.go | 45 + ja/codes/go/chapter_heap/heap_test.go | 101 ++ ja/codes/go/chapter_heap/my_heap.go | 140 ++ ja/codes/go/chapter_heap/top_k.go | 51 + .../go/chapter_searching/binary_search.go | 43 + .../chapter_searching/binary_search_edge.go | 31 + .../binary_search_insertion.go | 49 + .../chapter_searching/binary_search_test.go | 61 + .../go/chapter_searching/hashing_search.go | 29 + .../chapter_searching/hashing_search_test.go | 36 + .../go/chapter_searching/linear_search.go | 36 + .../chapter_searching/linear_search_test.go | 26 + ja/codes/go/chapter_searching/two_sum.go | 33 + ja/codes/go/chapter_searching/two_sum_test.go | 24 + ja/codes/go/chapter_sorting/bubble_sort.go | 38 + .../go/chapter_sorting/bubble_sort_test.go | 20 + ja/codes/go/chapter_sorting/bucket_sort.go | 37 + .../go/chapter_sorting/bucket_sort_test.go | 17 + ja/codes/go/chapter_sorting/counting_sort.go | 68 + .../go/chapter_sorting/counting_sort_test.go | 20 + ja/codes/go/chapter_sorting/heap_sort.go | 44 + ja/codes/go/chapter_sorting/heap_sort_test.go | 16 + ja/codes/go/chapter_sorting/insertion_sort.go | 20 + .../go/chapter_sorting/insertion_sort_test.go | 16 + ja/codes/go/chapter_sorting/merge_sort.go | 54 + .../go/chapter_sorting/merge_sort_test.go | 16 + ja/codes/go/chapter_sorting/quick_sort.go | 130 ++ .../go/chapter_sorting/quick_sort_test.go | 34 + ja/codes/go/chapter_sorting/radix_sort.go | 60 + .../go/chapter_sorting/radix_sort_test.go | 18 + ja/codes/go/chapter_sorting/selection_sort.go | 24 + .../go/chapter_sorting/selection_sort_test.go | 16 + .../go/chapter_stack_and_queue/array_deque.go | 121 ++ .../go/chapter_stack_and_queue/array_queue.go | 78 ++ .../go/chapter_stack_and_queue/array_stack.go | 55 + .../go/chapter_stack_and_queue/deque_test.go | 141 ++ .../linkedlist_deque.go | 85 ++ .../linkedlist_queue.go | 61 + .../linkedlist_stack.go | 61 + .../go/chapter_stack_and_queue/queue_test.go | 146 +++ .../go/chapter_stack_and_queue/stack_test.go | 130 ++ ja/codes/go/chapter_tree/array_binary_tree.go | 101 ++ .../go/chapter_tree/array_binary_tree_test.go | 47 + ja/codes/go/chapter_tree/avl_tree.go | 200 +++ ja/codes/go/chapter_tree/avl_tree_test.go | 54 + .../go/chapter_tree/binary_search_tree.go | 142 ++ .../chapter_tree/binary_search_tree_test.go | 45 + ja/codes/go/chapter_tree/binary_tree_bfs.go | 35 + .../go/chapter_tree/binary_tree_bfs_test.go | 24 + ja/codes/go/chapter_tree/binary_tree_dfs.go | 44 + .../go/chapter_tree/binary_tree_dfs_test.go | 35 + ja/codes/go/chapter_tree/binary_tree_test.go | 41 + ja/codes/go/go.mod | 3 + ja/codes/go/pkg/list_node.go | 31 + ja/codes/go/pkg/list_node_test.go | 16 + ja/codes/go/pkg/print_utils.go | 118 ++ ja/codes/go/pkg/tree_node.go | 78 ++ ja/codes/go/pkg/tree_node_test.go | 21 + ja/codes/go/pkg/vertex.go | 55 + ja/codes/java/.gitignore | 1 + .../chapter_array_and_linkedlist/array.java | 52 +- .../linked_list.java | 30 +- .../chapter_array_and_linkedlist/list.java | 38 +- .../chapter_array_and_linkedlist/my_list.java | 64 +- .../java/chapter_backtracking/n_queens.java | 30 +- .../chapter_backtracking/permutations_i.java | 16 +- .../chapter_backtracking/permutations_ii.java | 18 +- .../preorder_traversal_i_compact.java | 8 +- .../preorder_traversal_ii_compact.java | 12 +- .../preorder_traversal_iii_compact.java | 14 +- .../preorder_traversal_iii_template.java | 22 +- .../chapter_backtracking/subset_sum_i.java | 26 +- .../subset_sum_i_naive.java | 24 +- .../chapter_backtracking/subset_sum_ii.java | 30 +- .../iteration.java | 30 +- .../recursion.java | 22 +- .../space_complexity.java | 46 +- .../time_complexity.java | 68 +- .../worst_best_time_complexity.java | 16 +- .../binary_search_recur.java | 16 +- .../build_tree.java | 24 +- .../chapter_divide_and_conquer/hanota.java | 28 +- .../climbing_stairs_backtrack.java | 18 +- .../climbing_stairs_constraint_dp.java | 12 +- .../climbing_stairs_dfs.java | 8 +- .../climbing_stairs_dfs_mem.java | 10 +- .../climbing_stairs_dp.java | 16 +- .../coin_change.java | 30 +- .../coin_change_ii.java | 28 +- .../edit_distance.java | 68 +- .../chapter_dynamic_programming/knapsack.java | 52 +- .../min_cost_climbing_stairs_dp.java | 18 +- .../min_path_sum.java | 56 +- .../unbounded_knapsack.java | 26 +- .../chapter_graph/graph_adjacency_list.java | 26 +- .../chapter_graph/graph_adjacency_matrix.java | 46 +- ja/codes/java/chapter_graph/graph_bfs.java | 32 +- ja/codes/java/chapter_graph/graph_dfs.java | 22 +- .../chapter_greedy/coin_change_greedy.java | 30 +- .../chapter_greedy/fractional_knapsack.java | 24 +- .../java/chapter_greedy/max_capacity.java | 16 +- .../chapter_greedy/max_product_cutting.java | 18 +- .../java/chapter_hashing/array_hash_map.java | 46 +- .../java/chapter_hashing/built_in_hash.java | 18 +- ja/codes/java/chapter_hashing/hash_map.java | 32 +- .../chapter_hashing/hash_map_chaining.java | 54 +- .../hash_map_open_addressing.java | 78 +- .../java/chapter_hashing/simple_hash.java | 14 +- ja/codes/java/chapter_heap/heap.java | 28 +- ja/codes/java/chapter_heap/my_heap.java | 62 +- ja/codes/java/chapter_heap/top_k.java | 12 +- .../java/chapter_searching/binary_search.java | 34 +- .../chapter_searching/binary_search_edge.java | 20 +- .../binary_search_insertion.java | 26 +- .../chapter_searching/hashing_search.java | 18 +- .../java/chapter_searching/linear_search.java | 20 +- ja/codes/java/chapter_searching/two_sum.java | 24 +- .../java/chapter_sorting/bubble_sort.java | 22 +- .../java/chapter_sorting/bucket_sort.java | 18 +- .../java/chapter_sorting/counting_sort.java | 32 +- ja/codes/java/chapter_sorting/heap_sort.java | 22 +- .../java/chapter_sorting/insertion_sort.java | 12 +- ja/codes/java/chapter_sorting/merge_sort.java | 26 +- ja/codes/java/chapter_sorting/quick_sort.java | 88 +- ja/codes/java/chapter_sorting/radix_sort.java | 38 +- .../java/chapter_sorting/selection_sort.java | 10 +- .../chapter_stack_and_queue/array_deque.java | 76 +- .../chapter_stack_and_queue/array_queue.java | 37 +- .../chapter_stack_and_queue/array_stack.java | 14 +- .../java/chapter_stack_and_queue/deque.java | 22 +- .../linkedlist_deque.java | 83 +- .../linkedlist_queue.java | 18 +- .../linkedlist_stack.java | 14 +- .../java/chapter_stack_and_queue/queue.java | 8 +- .../java/chapter_stack_and_queue/stack.java | 8 +- .../java/chapter_tree/array_binary_tree.java | 44 +- ja/codes/java/chapter_tree/avl_tree.java | 100 +- .../java/chapter_tree/binary_search_tree.java | 64 +- ja/codes/java/chapter_tree/binary_tree.java | 6 +- .../java/chapter_tree/binary_tree_bfs.java | 18 +- .../java/chapter_tree/binary_tree_dfs.java | 20 +- ja/codes/java/utils/ListNode.java | 4 +- ja/codes/java/utils/PrintUtil.java | 26 +- ja/codes/java/utils/TreeNode.java | 36 +- ja/codes/java/utils/Vertex.java | 6 +- ja/codes/javascript/.prettierrc | 6 + .../chapter_array_and_linkedlist/array.js | 97 ++ .../linked_list.js | 82 ++ .../chapter_array_and_linkedlist/list.js | 57 + .../chapter_array_and_linkedlist/my_list.js | 141 ++ .../chapter_backtracking/n_queens.js | 55 + .../chapter_backtracking/permutations_i.js | 42 + .../chapter_backtracking/permutations_ii.js | 44 + .../preorder_traversal_i_compact.js | 33 + .../preorder_traversal_ii_compact.js | 40 + .../preorder_traversal_iii_compact.js | 41 + .../preorder_traversal_iii_template.js | 68 + .../chapter_backtracking/subset_sum_i.js | 46 + .../subset_sum_i_naive.js | 44 + .../chapter_backtracking/subset_sum_ii.js | 51 + .../iteration.js | 70 + .../recursion.js | 69 + .../space_complexity.js | 103 ++ .../time_complexity.js | 155 +++ .../worst_best_time_complexity.js | 43 + .../binary_search_recur.js | 39 + .../chapter_divide_and_conquer/build_tree.js | 44 + .../chapter_divide_and_conquer/hanota.js | 52 + .../climbing_stairs_backtrack.js | 34 + .../climbing_stairs_constraint_dp.js | 30 + .../climbing_stairs_dfs.js | 24 + .../climbing_stairs_dfs_mem.js | 30 + .../climbing_stairs_dp.js | 40 + .../coin_change.js | 66 + .../coin_change_ii.js | 64 + .../edit_distance.js | 135 ++ .../chapter_dynamic_programming/knapsack.js | 113 ++ .../min_cost_climbing_stairs_dp.js | 49 + .../min_path_sum.js | 121 ++ .../unbounded_knapsack.js | 63 + .../chapter_graph/graph_adjacency_list.js | 142 ++ .../chapter_graph/graph_adjacency_matrix.js | 132 ++ .../javascript/chapter_graph/graph_bfs.js | 61 + .../javascript/chapter_graph/graph_dfs.js | 54 + .../chapter_greedy/coin_change_greedy.js | 48 + .../chapter_greedy/fractional_knapsack.js | 46 + .../javascript/chapter_greedy/max_capacity.js | 34 + .../chapter_greedy/max_product_cutting.js | 33 + .../chapter_hashing/array_hash_map.js | 128 ++ .../javascript/chapter_hashing/hash_map.js | 44 + .../chapter_hashing/hash_map_chaining.js | 142 ++ .../hash_map_open_addressing.js | 177 +++ .../javascript/chapter_hashing/simple_hash.js | 60 + ja/codes/javascript/chapter_heap/my_heap.js | 158 +++ ja/codes/javascript/chapter_heap/top_k.js | 58 + .../chapter_searching/binary_search.js | 60 + .../chapter_searching/binary_search_edge.js | 45 + .../binary_search_insertion.js | 64 + .../chapter_searching/hashing_search.js | 45 + .../chapter_searching/linear_search.js | 47 + .../javascript/chapter_searching/two_sum.js | 46 + .../javascript/chapter_sorting/bubble_sort.js | 49 + .../javascript/chapter_sorting/bucket_sort.js | 39 + .../chapter_sorting/counting_sort.js | 65 + .../javascript/chapter_sorting/heap_sort.js | 49 + .../chapter_sorting/insertion_sort.js | 25 + .../javascript/chapter_sorting/merge_sort.js | 52 + .../javascript/chapter_sorting/quick_sort.js | 161 +++ .../javascript/chapter_sorting/radix_sort.js | 61 + .../chapter_sorting/selection_sort.js | 27 + .../chapter_stack_and_queue/array_deque.js | 156 +++ .../chapter_stack_and_queue/array_queue.js | 106 ++ .../chapter_stack_and_queue/array_stack.js | 75 ++ .../chapter_stack_and_queue/deque.js | 44 + .../linkedlist_deque.js | 167 +++ .../linkedlist_queue.js | 99 ++ .../linkedlist_stack.js | 88 ++ .../chapter_stack_and_queue/queue.js | 35 + .../chapter_stack_and_queue/stack.js | 35 + .../chapter_tree/array_binary_tree.js | 147 +++ ja/codes/javascript/chapter_tree/avl_tree.js | 208 +++ .../chapter_tree/binary_search_tree.js | 139 ++ .../javascript/chapter_tree/binary_tree.js | 35 + .../chapter_tree/binary_tree_bfs.js | 34 + .../chapter_tree/binary_tree_dfs.js | 60 + ja/codes/javascript/modules/ListNode.js | 31 + ja/codes/javascript/modules/PrintUtil.js | 86 ++ ja/codes/javascript/modules/TreeNode.js | 35 + ja/codes/javascript/modules/Vertex.js | 35 + ja/codes/javascript/test_all.js | 63 + .../chapter_array_and_linkedlist/array.kt | 102 ++ .../linked_list.kt | 88 ++ .../chapter_array_and_linkedlist/list.kt | 63 + .../chapter_array_and_linkedlist/my_list.kt | 139 ++ .../kotlin/chapter_backtracking/n_queens.kt | 85 ++ .../chapter_backtracking/permutations_i.kt | 53 + .../chapter_backtracking/permutations_ii.kt | 54 + .../preorder_traversal_i_compact.kt | 43 + .../preorder_traversal_ii_compact.kt | 51 + .../preorder_traversal_iii_compact.kt | 52 + .../preorder_traversal_iii_template.kt | 82 ++ .../chapter_backtracking/subset_sum_i.kt | 58 + .../subset_sum_i_naive.kt | 55 + .../chapter_backtracking/subset_sum_ii.kt | 62 + .../iteration.kt | 74 ++ .../recursion.kt | 78 ++ .../space_complexity.kt | 109 ++ .../time_complexity.kt | 168 +++ .../worst_best_time_complexity.kt | 45 + .../binary_search_recur.kt | 49 + .../chapter_divide_and_conquer/build_tree.kt | 55 + .../chapter_divide_and_conquer/hanota.kt | 56 + .../climbing_stairs_backtrack.kt | 45 + .../climbing_stairs_constraint_dp.kt | 35 + .../climbing_stairs_dfs.kt | 29 + .../climbing_stairs_dfs_mem.kt | 36 + .../climbing_stairs_dp.kt | 46 + .../coin_change.kt | 71 + .../coin_change_ii.kt | 66 + .../edit_distance.kt | 143 +++ .../chapter_dynamic_programming/knapsack.kt | 125 ++ .../min_cost_climbing_stairs_dp.kt | 51 + .../min_path_sum.kt | 132 ++ .../unbounded_knapsack.kt | 68 + .../chapter_graph/graph_adjacency_list.kt | 121 ++ .../chapter_graph/graph_adjacency_matrix.kt | 134 ++ ja/codes/kotlin/chapter_graph/graph_bfs.kt | 65 + ja/codes/kotlin/chapter_graph/graph_dfs.kt | 60 + .../chapter_greedy/coin_change_greedy.kt | 53 + .../chapter_greedy/fractional_knapsack.kt | 51 + .../kotlin/chapter_greedy/max_capacity.kt | 41 + .../chapter_greedy/max_product_cutting.kt | 39 + .../kotlin/chapter_hashing/array_hash_map.kt | 126 ++ .../kotlin/chapter_hashing/built_in_hash.kt | 36 + ja/codes/kotlin/chapter_hashing/hash_map.kt | 50 + .../chapter_hashing/hash_map_chaining.kt | 145 +++ .../hash_map_open_addressing.kt | 161 +++ .../kotlin/chapter_hashing/simple_hash.kt | 64 + ja/codes/kotlin/chapter_heap/heap.kt | 66 + ja/codes/kotlin/chapter_heap/my_heap.kt | 160 +++ ja/codes/kotlin/chapter_heap/top_k.kt | 38 + .../kotlin/chapter_searching/binary_search.kt | 59 + .../chapter_searching/binary_search_edge.kt | 48 + .../binary_search_insertion.kt | 65 + .../chapter_searching/hashing_search.kt | 49 + .../kotlin/chapter_searching/linear_search.kt | 50 + ja/codes/kotlin/chapter_searching/two_sum.kt | 49 + .../kotlin/chapter_sorting/bubble_sort.kt | 53 + .../kotlin/chapter_sorting/bucket_sort.kt | 44 + .../kotlin/chapter_sorting/counting_sort.kt | 80 ++ ja/codes/kotlin/chapter_sorting/heap_sort.kt | 55 + .../kotlin/chapter_sorting/insertion_sort.kt | 29 + ja/codes/kotlin/chapter_sorting/merge_sort.kt | 56 + ja/codes/kotlin/chapter_sorting/quick_sort.kt | 121 ++ ja/codes/kotlin/chapter_sorting/radix_sort.kt | 68 + .../kotlin/chapter_sorting/selection_sort.kt | 32 + .../chapter_stack_and_queue/array_deque.kt | 145 +++ .../chapter_stack_and_queue/array_queue.kt | 110 ++ .../chapter_stack_and_queue/array_stack.kt | 75 ++ .../kotlin/chapter_stack_and_queue/deque.kt | 45 + .../linkedlist_deque.kt | 163 +++ .../linkedlist_queue.kt | 98 ++ .../linkedlist_stack.kt | 87 ++ .../kotlin/chapter_stack_and_queue/queue.kt | 39 + .../kotlin/chapter_stack_and_queue/stack.kt | 39 + .../kotlin/chapter_tree/array_binary_tree.kt | 127 ++ ja/codes/kotlin/chapter_tree/avl_tree.kt | 223 ++++ .../kotlin/chapter_tree/binary_search_tree.kt | 157 +++ ja/codes/kotlin/chapter_tree/binary_tree.kt | 40 + .../kotlin/chapter_tree/binary_tree_bfs.kt | 42 + .../kotlin/chapter_tree/binary_tree_dfs.kt | 64 + ja/codes/kotlin/utils/ListNode.kt | 25 + ja/codes/kotlin/utils/PrintUtil.kt | 107 ++ ja/codes/kotlin/utils/TreeNode.kt | 69 + ja/codes/kotlin/utils/Vertex.kt | 30 + ja/codes/python/.gitignore | 1 + .../chapter_array_and_linkedlist/array.py | 54 +- .../linked_list.py | 24 +- .../chapter_array_and_linkedlist/list.py | 24 +- .../chapter_array_and_linkedlist/my_list.py | 48 +- .../python/chapter_backtracking/n_queens.py | 32 +- .../chapter_backtracking/permutations_i.py | 16 +- .../chapter_backtracking/permutations_ii.py | 20 +- .../preorder_traversal_i_compact.py | 10 +- .../preorder_traversal_ii_compact.py | 14 +- .../preorder_traversal_iii_compact.py | 14 +- .../preorder_traversal_iii_template.py | 24 +- .../chapter_backtracking/subset_sum_i.py | 26 +- .../subset_sum_i_naive.py | 24 +- .../chapter_backtracking/subset_sum_ii.py | 30 +- .../iteration.py | 34 +- .../recursion.py | 18 +- .../space_complexity.py | 36 +- .../time_complexity.py | 90 +- .../worst_best_time_complexity.py | 14 +- .../binary_search_recur.py | 14 +- .../chapter_divide_and_conquer/build_tree.py | 22 +- .../chapter_divide_and_conquer/hanota.py | 28 +- .../climbing_stairs_backtrack.py | 18 +- .../climbing_stairs_constraint_dp.py | 12 +- .../climbing_stairs_dfs.py | 8 +- .../climbing_stairs_dfs_mem.py | 16 +- .../climbing_stairs_dp.py | 16 +- .../coin_change.py | 28 +- .../coin_change_ii.py | 26 +- .../edit_distance.py | 74 +- .../chapter_dynamic_programming/knapsack.py | 54 +- .../min_cost_climbing_stairs_dp.py | 18 +- .../min_path_sum.py | 54 +- .../unbounded_knapsack.py | 24 +- .../chapter_graph/graph_adjacency_list.py | 32 +- .../chapter_graph/graph_adjacency_matrix.py | 56 +- ja/codes/python/chapter_graph/graph_bfs.py | 34 +- ja/codes/python/chapter_graph/graph_dfs.py | 26 +- .../chapter_greedy/coin_change_greedy.py | 30 +- .../chapter_greedy/fractional_knapsack.py | 24 +- .../python/chapter_greedy/max_capacity.py | 16 +- .../chapter_greedy/max_product_cutting.py | 18 +- .../python/chapter_hashing/array_hash_map.py | 48 +- .../python/chapter_hashing/built_in_hash.py | 6 +- ja/codes/python/chapter_hashing/hash_map.py | 32 +- .../chapter_hashing/hash_map_chaining.py | 56 +- .../hash_map_open_addressing.py | 80 +- .../python/chapter_hashing/simple_hash.py | 16 +- ja/codes/python/chapter_heap/heap.py | 38 +- ja/codes/python/chapter_heap/my_heap.py | 68 +- ja/codes/python/chapter_heap/top_k.py | 12 +- .../python/chapter_searching/binary_search.py | 40 +- .../chapter_searching/binary_search_edge.py | 28 +- .../binary_search_insertion.py | 38 +- .../chapter_searching/hashing_search.py | 18 +- .../python/chapter_searching/linear_search.py | 20 +- ja/codes/python/chapter_searching/two_sum.py | 24 +- .../python/chapter_sorting/bubble_sort.py | 22 +- .../python/chapter_sorting/bucket_sort.py | 16 +- .../python/chapter_sorting/counting_sort.py | 34 +- ja/codes/python/chapter_sorting/heap_sort.py | 22 +- .../python/chapter_sorting/insertion_sort.py | 12 +- ja/codes/python/chapter_sorting/merge_sort.py | 30 +- ja/codes/python/chapter_sorting/quick_sort.py | 90 +- ja/codes/python/chapter_sorting/radix_sort.py | 38 +- .../python/chapter_sorting/selection_sort.py | 10 +- .../chapter_stack_and_queue/array_deque.py | 74 +- .../chapter_stack_and_queue/array_queue.py | 32 +- .../chapter_stack_and_queue/array_stack.py | 18 +- .../python/chapter_stack_and_queue/deque.py | 36 +- .../linkedlist_deque.py | 84 +- .../linkedlist_queue.py | 30 +- .../linkedlist_stack.py | 16 +- .../python/chapter_stack_and_queue/queue.py | 16 +- .../python/chapter_stack_and_queue/stack.py | 12 +- .../python/chapter_tree/array_binary_tree.py | 44 +- ja/codes/python/chapter_tree/avl_tree.py | 102 +- .../python/chapter_tree/binary_search_tree.py | 66 +- ja/codes/python/chapter_tree/binary_tree.py | 16 +- .../python/chapter_tree/binary_tree_bfs.py | 18 +- .../python/chapter_tree/binary_tree_dfs.py | 20 +- ja/codes/python/modules/__init__.py | 6 +- ja/codes/python/modules/list_node.py | 8 +- ja/codes/python/modules/print_util.py | 16 +- ja/codes/python/modules/tree_node.py | 44 +- ja/codes/python/modules/vertex.py | 6 +- ja/codes/python/test_all.py | 16 +- .../chapter_array_and_linkedlist/array.md | 23 + .../linked_list.md | 17 + .../chapter_array_and_linkedlist/my_list.md | 8 + .../chapter_backtracking/n_queens.md | 8 + .../chapter_backtracking/permutations_i.md | 8 + .../chapter_backtracking/permutations_ii.md | 8 + .../preorder_traversal_i_compact.md | 8 + .../preorder_traversal_ii_compact.md | 8 + .../preorder_traversal_iii_compact.md | 8 + .../preorder_traversal_iii_template.md | 8 + .../chapter_backtracking/subset_sum_i.md | 8 + .../subset_sum_i_naive.md | 8 + .../chapter_backtracking/subset_sum_ii.md | 8 + .../iteration.md | 29 + .../recursion.md | 17 + .../space_complexity.md | 23 + .../time_complexity.md | 38 + .../worst_best_time_complexity.md | 8 + .../binary_search_recur.md | 8 + .../chapter_divide_and_conquer/build_tree.md | 8 + .../chapter_divide_and_conquer/hanota.md | 8 + .../climbing_stairs_backtrack.md | 8 + .../climbing_stairs_constraint_dp.md | 8 + .../climbing_stairs_dfs.md | 8 + .../climbing_stairs_dfs_mem.md | 8 + .../climbing_stairs_dp.md | 11 + .../coin_change.md | 11 + .../coin_change_ii.md | 11 + .../edit_distance.md | 11 + .../chapter_dynamic_programming/knapsack.md | 17 + .../min_cost_climbing_stairs_dp.md | 11 + .../min_path_sum.md | 17 + .../unbounded_knapsack.md | 11 + .../chapter_graph/graph_adjacency_list.md | 8 + .../chapter_graph/graph_adjacency_matrix.md | 8 + .../pythontutor/chapter_graph/graph_bfs.md | 8 + .../pythontutor/chapter_graph/graph_dfs.md | 8 + .../chapter_greedy/coin_change_greedy.md | 8 + .../chapter_greedy/fractional_knapsack.md | 8 + .../chapter_greedy/max_capacity.md | 8 + .../chapter_greedy/max_product_cutting.md | 8 + .../chapter_hashing/array_hash_map.md | 8 + .../chapter_hashing/hash_map_chaining.md | 8 + .../chapter_hashing/simple_hash.md | 8 + ja/codes/pythontutor/chapter_heap/my_heap.md | 20 + ja/codes/pythontutor/chapter_heap/top_k.md | 8 + .../chapter_searching/binary_search.md | 11 + .../chapter_searching/binary_search_edge.md | 11 + .../binary_search_insertion.md | 11 + .../pythontutor/chapter_searching/two_sum.md | 11 + .../chapter_sorting/bubble_sort.md | 11 + .../chapter_sorting/bucket_sort.md | 8 + .../chapter_sorting/counting_sort.md | 11 + .../pythontutor/chapter_sorting/heap_sort.md | 8 + .../chapter_sorting/insertion_sort.md | 8 + .../pythontutor/chapter_sorting/merge_sort.md | 8 + .../pythontutor/chapter_sorting/quick_sort.md | 17 + .../pythontutor/chapter_sorting/radix_sort.md | 8 + .../chapter_sorting/selection_sort.md | 8 + .../chapter_stack_and_queue/array_queue.md | 8 + .../chapter_stack_and_queue/array_stack.md | 8 + .../linkedlist_queue.md | 8 + .../linkedlist_stack.md | 8 + .../chapter_tree/array_binary_tree.md | 8 + .../chapter_tree/binary_search_tree.md | 14 + .../chapter_tree/binary_tree_bfs.md | 8 + .../chapter_tree/binary_tree_dfs.md | 8 + .../chapter_array_and_linkedlist/array.rb | 108 ++ .../linked_list.rb | 83 ++ .../ruby/chapter_array_and_linkedlist/list.rb | 60 + .../chapter_array_and_linkedlist/my_list.rb | 132 ++ .../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 + .../iteration.rb | 79 ++ .../recursion.rb | 70 + .../space_complexity.rb | 92 ++ .../time_complexity.rb | 165 +++ .../worst_best_time_complexity.rb | 35 + .../binary_search_recur.rb | 42 + .../chapter_divide_and_conquer/build_tree.rb | 46 + .../ruby/chapter_divide_and_conquer/hanota.rb | 55 + .../climbing_stairs_backtrack.rb | 37 + .../climbing_stairs_constraint_dp.rb | 31 + .../climbing_stairs_dfs.rb | 26 + .../climbing_stairs_dfs_mem.rb | 33 + .../climbing_stairs_dp.rb | 40 + .../coin_change.rb | 65 + .../coin_change_ii.rb | 63 + .../edit_distance.rb | 115 ++ .../chapter_dynamic_programming/knapsack.rb | 99 ++ .../min_cost_climbing_stairs_dp.rb | 39 + .../min_path_sum.rb | 93 ++ .../unbounded_knapsack.rb | 61 + .../chapter_graph/graph_adjacency_list.rb | 116 ++ .../chapter_graph/graph_adjacency_matrix.rb | 116 ++ ja/codes/ruby/chapter_graph/graph_bfs.rb | 61 + ja/codes/ruby/chapter_graph/graph_dfs.rb | 54 + .../ruby/chapter_greedy/coin_change_greedy.rb | 50 + .../chapter_greedy/fractional_knapsack.rb | 51 + ja/codes/ruby/chapter_greedy/max_capacity.rb | 37 + .../chapter_greedy/max_product_cutting.rb | 28 + .../ruby/chapter_hashing/array_hash_map.rb | 121 ++ .../ruby/chapter_hashing/built_in_hash.rb | 34 + ja/codes/ruby/chapter_hashing/hash_map.rb | 44 + .../ruby/chapter_hashing/hash_map_chaining.rb | 128 ++ .../hash_map_open_addressing.rb | 147 +++ ja/codes/ruby/chapter_hashing/simple_hash.rb | 62 + ja/codes/ruby/chapter_heap/my_heap.rb | 147 +++ ja/codes/ruby/chapter_heap/top_k.rb | 64 + .../ruby/chapter_searching/binary_search.rb | 63 + .../chapter_searching/binary_search_edge.rb | 47 + .../binary_search_insertion.rb | 68 + .../ruby/chapter_searching/hashing_search.rb | 47 + .../ruby/chapter_searching/linear_search.rb | 44 + ja/codes/ruby/chapter_searching/two_sum.rb | 46 + ja/codes/ruby/chapter_sorting/bubble_sort.rb | 51 + ja/codes/ruby/chapter_sorting/bucket_sort.rb | 43 + .../ruby/chapter_sorting/counting_sort.rb | 62 + ja/codes/ruby/chapter_sorting/heap_sort.rb | 45 + .../ruby/chapter_sorting/insertion_sort.rb | 26 + ja/codes/ruby/chapter_sorting/merge_sort.rb | 60 + ja/codes/ruby/chapter_sorting/quick_sort.rb | 153 +++ ja/codes/ruby/chapter_sorting/radix_sort.rb | 70 + .../ruby/chapter_sorting/selection_sort.rb | 29 + .../chapter_stack_and_queue/array_deque.rb | 145 +++ .../chapter_stack_and_queue/array_queue.rb | 107 ++ .../chapter_stack_and_queue/array_stack.rb | 78 ++ .../ruby/chapter_stack_and_queue/deque.rb | 42 + .../linkedlist_deque.rb | 168 +++ .../linkedlist_queue.rb | 101 ++ .../linkedlist_stack.rb | 87 ++ .../ruby/chapter_stack_and_queue/queue.rb | 38 + .../ruby/chapter_stack_and_queue/stack.rb | 37 + .../ruby/chapter_tree/array_binary_tree.rb | 124 ++ ja/codes/ruby/chapter_tree/avl_tree.rb | 216 ++++ .../ruby/chapter_tree/binary_search_tree.rb | 161 +++ ja/codes/ruby/chapter_tree/binary_tree.rb | 38 + ja/codes/ruby/chapter_tree/binary_tree_bfs.rb | 36 + ja/codes/ruby/chapter_tree/binary_tree_dfs.rb | 62 + ja/codes/ruby/test_all.rb | 23 + ja/codes/ruby/utils/list_node.rb | 38 + ja/codes/ruby/utils/print_util.rb | 80 ++ ja/codes/ruby/utils/tree_node.rb | 53 + ja/codes/ruby/utils/vertex.rb | 24 + ja/codes/rust/.gitignore | 2 + ja/codes/rust/Cargo.toml | 413 ++++++ .../chapter_array_and_linkedlist/array.rs | 111 ++ .../linked_list.rs | 100 ++ .../rust/chapter_array_and_linkedlist/list.rs | 71 + .../chapter_array_and_linkedlist/my_list.rs | 164 +++ .../rust/chapter_backtracking/n_queens.rs | 76 ++ .../chapter_backtracking/permutations_i.rs | 46 + .../chapter_backtracking/permutations_ii.rs | 50 + .../preorder_traversal_i_compact.rs | 41 + .../preorder_traversal_ii_compact.rs | 52 + .../preorder_traversal_iii_compact.rs | 53 + .../preorder_traversal_iii_template.rs | 88 ++ .../rust/chapter_backtracking/subset_sum_i.rs | 56 + .../subset_sum_i_naive.rs | 54 + .../chapter_backtracking/subset_sum_ii.rs | 61 + .../iteration.rs | 74 ++ .../recursion.rs | 76 ++ .../space_complexity.rs | 114 ++ .../time_complexity.rs | 170 +++ .../worst_best_time_complexity.rs | 42 + .../binary_search_recur.rs | 41 + .../chapter_divide_and_conquer/build_tree.rs | 56 + .../rust/chapter_divide_and_conquer/hanota.rs | 55 + .../climbing_stairs_backtrack.rs | 41 + .../climbing_stairs_constraint_dp.rs | 33 + .../climbing_stairs_dfs.rs | 29 + .../climbing_stairs_dfs_mem.rs | 37 + .../climbing_stairs_dp.rs | 48 + .../coin_change.rs | 75 ++ .../coin_change_ii.rs | 64 + .../edit_distance.rs | 145 +++ .../chapter_dynamic_programming/knapsack.rs | 113 ++ .../min_cost_climbing_stairs_dp.rs | 52 + .../min_path_sum.rs | 120 ++ .../unbounded_knapsack.rs | 60 + .../chapter_graph/graph_adjacency_list.rs | 135 ++ .../chapter_graph/graph_adjacency_matrix.rs | 136 ++ ja/codes/rust/chapter_graph/graph_bfs.rs | 69 + ja/codes/rust/chapter_graph/graph_dfs.rs | 61 + .../rust/chapter_greedy/coin_change_greedy.rs | 54 + .../chapter_greedy/fractional_knapsack.rs | 59 + ja/codes/rust/chapter_greedy/max_capacity.rs | 36 + .../chapter_greedy/max_product_cutting.rs | 35 + .../rust/chapter_hashing/array_hash_map.rs | 124 ++ .../rust/chapter_hashing/build_in_hash.rs | 49 + ja/codes/rust/chapter_hashing/hash_map.rs | 48 + .../rust/chapter_hashing/hash_map_chaining.rs | 160 +++ .../hash_map_open_addressing.rs | 181 +++ ja/codes/rust/chapter_hashing/simple_hash.rs | 70 + ja/codes/rust/chapter_heap/heap.rs | 71 + ja/codes/rust/chapter_heap/my_heap.rs | 165 +++ ja/codes/rust/chapter_heap/top_k.rs | 39 + .../rust/chapter_searching/binary_search.rs | 65 + .../chapter_searching/binary_search_edge.rs | 50 + .../binary_search_insertion.rs | 61 + .../rust/chapter_searching/hashing_search.rs | 50 + .../rust/chapter_searching/linear_search.rs | 54 + ja/codes/rust/chapter_searching/two_sum.rs | 52 + ja/codes/rust/chapter_sorting/bubble_sort.rs | 53 + ja/codes/rust/chapter_sorting/bucket_sort.rs | 43 + .../rust/chapter_sorting/counting_sort.rs | 70 + ja/codes/rust/chapter_sorting/heap_sort.rs | 54 + .../rust/chapter_sorting/insertion_sort.rs | 29 + ja/codes/rust/chapter_sorting/merge_sort.rs | 66 + ja/codes/rust/chapter_sorting/quick_sort.rs | 148 +++ ja/codes/rust/chapter_sorting/radix_sort.rs | 63 + .../rust/chapter_sorting/selection_sort.rs | 35 + .../chapter_stack_and_queue/array_deque.rs | 160 +++ .../chapter_stack_and_queue/array_queue.rs | 125 ++ .../chapter_stack_and_queue/array_stack.rs | 86 ++ .../rust/chapter_stack_and_queue/deque.rs | 49 + .../linkedlist_deque.rs | 218 ++++ .../linkedlist_queue.rs | 126 ++ .../linkedlist_stack.rs | 105 ++ .../rust/chapter_stack_and_queue/queue.rs | 41 + .../rust/chapter_stack_and_queue/stack.rs | 40 + .../rust/chapter_tree/array_binary_tree.rs | 192 +++ ja/codes/rust/chapter_tree/avl_tree.rs | 297 +++++ .../rust/chapter_tree/binary_search_tree.rs | 195 +++ ja/codes/rust/chapter_tree/binary_tree.rs | 38 + ja/codes/rust/chapter_tree/binary_tree_bfs.rs | 45 + ja/codes/rust/chapter_tree/binary_tree_dfs.rs | 87 ++ ja/codes/rust/src/include/list_node.rs | 57 + ja/codes/rust/src/include/mod.rs | 16 + ja/codes/rust/src/include/print_util.rs | 103 ++ ja/codes/rust/src/include/tree_node.rs | 92 ++ ja/codes/rust/src/include/vertex.rs | 27 + ja/codes/rust/src/lib.rs | 1 + ja/codes/swift/.gitignore | 130 ++ ja/codes/swift/Package.resolved | 14 + ja/codes/swift/Package.swift | 206 +++ .../chapter_array_and_linkedlist/array.swift | 107 ++ .../linked_list.swift | 90 ++ .../chapter_array_and_linkedlist/list.swift | 63 + .../my_list.swift | 146 +++ .../swift/chapter_backtracking/n_queens.swift | 67 + .../chapter_backtracking/permutations_i.swift | 50 + .../permutations_ii.swift | 52 + .../preorder_traversal_i_compact.swift | 43 + .../preorder_traversal_ii_compact.swift | 51 + .../preorder_traversal_iii_compact.swift | 52 + .../preorder_traversal_iii_template.swift | 76 ++ .../chapter_backtracking/subset_sum_i.swift | 53 + .../subset_sum_i_naive.swift | 51 + .../chapter_backtracking/subset_sum_ii.swift | 58 + .../iteration.swift | 75 ++ .../recursion.swift | 79 ++ .../space_complexity.swift | 98 ++ .../time_complexity.swift | 172 +++ .../worst_best_time_complexity.swift | 40 + .../binary_search_recur.swift | 44 + .../build_tree.swift | 47 + .../chapter_divide_and_conquer/hanota.swift | 58 + .../climbing_stairs_backtrack.swift | 44 + .../climbing_stairs_constraint_dp.swift | 36 + .../climbing_stairs_dfs.swift | 32 + .../climbing_stairs_dfs_mem.swift | 40 + .../climbing_stairs_dp.swift | 49 + .../coin_change.swift | 69 + .../coin_change_ii.swift | 67 + .../edit_distance.swift | 147 +++ .../knapsack.swift | 110 ++ .../min_cost_climbing_stairs_dp.swift | 51 + .../min_path_sum.swift | 123 ++ .../unbounded_knapsack.swift | 63 + .../chapter_graph/graph_adjacency_list.swift | 121 ++ .../graph_adjacency_list_target.swift | 121 ++ .../graph_adjacency_matrix.swift | 130 ++ ja/codes/swift/chapter_graph/graph_bfs.swift | 56 + ja/codes/swift/chapter_graph/graph_dfs.swift | 54 + .../chapter_greedy/coin_change_greedy.swift | 54 + .../chapter_greedy/fractional_knapsack.swift | 57 + .../swift/chapter_greedy/max_capacity.swift | 38 + .../chapter_greedy/max_product_cutting.swift | 43 + .../chapter_hashing/array_hash_map.swift | 110 ++ .../swift/chapter_hashing/built_in_hash.swift | 37 + ja/codes/swift/chapter_hashing/hash_map.swift | 51 + .../chapter_hashing/hash_map_chaining.swift | 138 ++ .../hash_map_open_addressing.swift | 164 +++ .../swift/chapter_hashing/simple_hash.swift | 73 ++ ja/codes/swift/chapter_heap/heap.swift | 62 + ja/codes/swift/chapter_heap/my_heap.swift | 163 +++ ja/codes/swift/chapter_heap/top_k.swift | 36 + .../chapter_searching/binary_search.swift | 62 + .../binary_search_edge.swift | 51 + .../binary_search_insertion.swift | 71 + .../binary_search_insertion_target.swift | 71 + .../chapter_searching/hashing_search.swift | 50 + .../chapter_searching/linear_search.swift | 53 + .../swift/chapter_searching/two_sum.swift | 49 + .../swift/chapter_sorting/bubble_sort.swift | 51 + .../swift/chapter_sorting/bucket_sort.swift | 43 + .../swift/chapter_sorting/counting_sort.swift | 70 + .../swift/chapter_sorting/heap_sort.swift | 55 + .../chapter_sorting/insertion_sort.swift | 30 + .../swift/chapter_sorting/merge_sort.swift | 65 + .../swift/chapter_sorting/quick_sort.swift | 114 ++ .../swift/chapter_sorting/radix_sort.swift | 79 ++ .../chapter_sorting/selection_sort.swift | 31 + .../chapter_stack_and_queue/array_deque.swift | 148 +++ .../chapter_stack_and_queue/array_queue.swift | 113 ++ .../chapter_stack_and_queue/array_stack.swift | 85 ++ .../swift/chapter_stack_and_queue/deque.swift | 44 + .../linkedlist_deque.swift | 180 +++ .../linkedlist_queue.swift | 107 ++ .../linkedlist_stack.swift | 96 ++ .../swift/chapter_stack_and_queue/queue.swift | 40 + .../swift/chapter_stack_and_queue/stack.swift | 39 + .../chapter_tree/array_binary_tree.swift | 141 ++ ja/codes/swift/chapter_tree/avl_tree.swift | 230 ++++ .../chapter_tree/binary_search_tree.swift | 173 +++ ja/codes/swift/chapter_tree/binary_tree.swift | 40 + .../swift/chapter_tree/binary_tree_bfs.swift | 42 + .../swift/chapter_tree/binary_tree_dfs.swift | 70 + ja/codes/swift/utils/ListNode.swift | 33 + ja/codes/swift/utils/Pair.swift | 20 + ja/codes/swift/utils/PrintUtil.swift | 93 ++ ja/codes/swift/utils/TreeNode.swift | 71 + ja/codes/swift/utils/Vertex.swift | 32 + ja/codes/typescript/.gitignore | 2 + ja/codes/typescript/.prettierrc | 6 + .../chapter_array_and_linkedlist/array.ts | 101 ++ .../linked_list.ts | 86 ++ .../chapter_array_and_linkedlist/list.ts | 59 + .../chapter_array_and_linkedlist/my_list.ts | 141 ++ .../chapter_backtracking/n_queens.ts | 65 + .../chapter_backtracking/permutations_i.ts | 49 + .../chapter_backtracking/permutations_ii.ts | 51 + .../preorder_traversal_i_compact.ts | 36 + .../preorder_traversal_ii_compact.ts | 47 + .../preorder_traversal_iii_compact.ts | 48 + .../preorder_traversal_iii_template.ts | 75 ++ .../chapter_backtracking/subset_sum_i.ts | 54 + .../subset_sum_i_naive.ts | 52 + .../chapter_backtracking/subset_sum_ii.ts | 59 + .../iteration.ts | 72 ++ .../recursion.ts | 70 + .../space_complexity.ts | 103 ++ .../time_complexity.ts | 157 +++ .../worst_best_time_complexity.ts | 45 + .../binary_search_recur.ts | 41 + .../chapter_divide_and_conquer/build_tree.ts | 50 + .../chapter_divide_and_conquer/hanota.ts | 52 + .../climbing_stairs_backtrack.ts | 41 + .../climbing_stairs_constraint_dp.ts | 32 + .../climbing_stairs_dfs.ts | 26 + .../climbing_stairs_dfs_mem.ts | 32 + .../climbing_stairs_dp.ts | 42 + .../coin_change.ts | 68 + .../coin_change_ii.ts | 66 + .../edit_distance.ts | 148 +++ .../chapter_dynamic_programming/knapsack.ts | 134 ++ .../min_cost_climbing_stairs_dp.ts | 51 + .../min_path_sum.ts | 132 ++ .../unbounded_knapsack.ts | 73 ++ .../chapter_graph/graph_adjacency_list.ts | 140 ++ .../chapter_graph/graph_adjacency_matrix.ts | 134 ++ .../typescript/chapter_graph/graph_bfs.ts | 61 + .../typescript/chapter_graph/graph_dfs.ts | 58 + .../chapter_greedy/coin_change_greedy.ts | 50 + .../chapter_greedy/fractional_knapsack.ts | 50 + .../typescript/chapter_greedy/max_capacity.ts | 36 + .../chapter_greedy/max_product_cutting.ts | 35 + .../chapter_hashing/array_hash_map.ts | 134 ++ .../typescript/chapter_hashing/hash_map.ts | 46 + .../chapter_hashing/hash_map_chaining.ts | 146 +++ .../hash_map_open_addressing.ts | 182 +++ .../typescript/chapter_hashing/simple_hash.ts | 60 + ja/codes/typescript/chapter_heap/my_heap.ts | 155 +++ ja/codes/typescript/chapter_heap/top_k.ts | 58 + .../chapter_searching/binary_search.ts | 65 + .../chapter_searching/binary_search_edge.ts | 46 + .../binary_search_insertion.ts | 65 + .../chapter_searching/hashing_search.ts | 50 + .../chapter_searching/linear_search.ts | 52 + .../typescript/chapter_searching/two_sum.ts | 49 + .../typescript/chapter_sorting/bubble_sort.ts | 51 + .../typescript/chapter_sorting/bucket_sort.ts | 41 + .../chapter_sorting/counting_sort.ts | 67 + .../typescript/chapter_sorting/heap_sort.ts | 51 + .../chapter_sorting/insertion_sort.ts | 27 + .../typescript/chapter_sorting/merge_sort.ts | 54 + .../typescript/chapter_sorting/quick_sort.ts | 180 +++ .../typescript/chapter_sorting/radix_sort.ts | 63 + .../chapter_sorting/selection_sort.ts | 29 + .../chapter_stack_and_queue/array_deque.ts | 158 +++ .../chapter_stack_and_queue/array_queue.ts | 109 ++ .../chapter_stack_and_queue/array_stack.ts | 77 ++ .../chapter_stack_and_queue/deque.ts | 46 + .../linkedlist_deque.ts | 167 +++ .../linkedlist_queue.ts | 102 ++ .../linkedlist_stack.ts | 91 ++ .../chapter_stack_and_queue/queue.ts | 37 + .../chapter_stack_and_queue/stack.ts | 37 + .../chapter_tree/array_binary_tree.ts | 151 +++ ja/codes/typescript/chapter_tree/avl_tree.ts | 222 ++++ .../chapter_tree/binary_search_tree.ts | 146 +++ .../typescript/chapter_tree/binary_tree.ts | 37 + .../chapter_tree/binary_tree_bfs.ts | 41 + .../chapter_tree/binary_tree_dfs.ts | 69 + ja/codes/typescript/modules/ListNode.ts | 28 + ja/codes/typescript/modules/PrintUtil.ts | 93 ++ ja/codes/typescript/modules/TreeNode.ts | 37 + ja/codes/typescript/modules/Vertex.ts | 33 + ja/codes/typescript/package.json | 11 + ja/codes/typescript/tsconfig.json | 12 + ja/codes/zig/.gitignore | 4 + ja/codes/zig/.vscode/launch.json | 14 + ja/codes/zig/.vscode/settings.json | 3 + ja/codes/zig/.vscode/tasks.json | 10 + ja/codes/zig/build.zig | 169 +++ .../chapter_array_and_linkedlist/array.zig | 131 ++ .../linked_list.zig | 106 ++ .../zig/chapter_array_and_linkedlist/list.zig | 78 ++ .../chapter_array_and_linkedlist/my_list.zig | 233 ++++ .../iteration.zig | 91 ++ .../recursion.zig | 88 ++ .../space_complexity.zig | 142 ++ .../time_complexity.zig | 184 +++ .../worst_best_time_complexity.zig | 53 + .../climbing_stairs_backtrack.zig | 44 + .../climbing_stairs_constraint_dp.zig | 35 + .../climbing_stairs_dfs.zig | 31 + .../climbing_stairs_dfs_mem.zig | 39 + .../climbing_stairs_dp.zig | 51 + .../coin_change.zig | 77 ++ .../coin_change_ii.zig | 66 + .../edit_distance.zig | 146 +++ .../chapter_dynamic_programming/knapsack.zig | 110 ++ .../min_cost_climbing_stairs_dp.zig | 54 + .../min_path_sum.zig | 122 ++ .../unbounded_knapsack.zig | 62 + .../zig/chapter_hashing/array_hash_map.zig | 162 +++ ja/codes/zig/chapter_hashing/hash_map.zig | 54 + ja/codes/zig/chapter_heap/heap.zig | 80 ++ ja/codes/zig/chapter_heap/my_heap.zig | 186 +++ .../zig/chapter_searching/binary_search.zig | 64 + .../zig/chapter_searching/hashing_search.zig | 57 + .../zig/chapter_searching/linear_search.zig | 54 + ja/codes/zig/chapter_searching/two_sum.zig | 58 + ja/codes/zig/chapter_sorting/bubble_sort.zig | 61 + .../zig/chapter_sorting/insertion_sort.zig | 31 + ja/codes/zig/chapter_sorting/merge_sort.zig | 67 + ja/codes/zig/chapter_sorting/quick_sort.zig | 162 +++ ja/codes/zig/chapter_sorting/radix_sort.zig | 77 ++ .../chapter_stack_and_queue/array_queue.zig | 140 ++ .../chapter_stack_and_queue/array_stack.zig | 97 ++ .../zig/chapter_stack_and_queue/deque.zig | 51 + .../linkedlist_deque.zig | 207 +++ .../linkedlist_queue.zig | 127 ++ .../linkedlist_stack.zig | 118 ++ .../zig/chapter_stack_and_queue/queue.zig | 46 + .../zig/chapter_stack_and_queue/stack.zig | 43 + ja/codes/zig/chapter_tree/avl_tree.zig | 249 ++++ .../zig/chapter_tree/binary_search_tree.zig | 182 +++ ja/codes/zig/chapter_tree/binary_tree.zig | 39 + ja/codes/zig/chapter_tree/binary_tree_bfs.zig | 57 + ja/codes/zig/chapter_tree/binary_tree_dfs.zig | 70 + ja/codes/zig/include/PrintUtil.zig | 42 + ja/codes/zig/include/include.zig | 7 + ja/codes/zig/main.zig | 25 + ja/codes/zig/utils/ListNode.zig | 49 + ja/codes/zig/utils/TreeNode.zig | 63 + ja/codes/zig/utils/format.zig | 140 ++ ja/codes/zig/utils/utils.zig | 8 + ja/docs/chapter_appendix/contribution.md | 42 +- ja/docs/chapter_appendix/installation.md | 82 +- ja/docs/chapter_appendix/terminology.md | 262 ++-- ja/docs/chapter_array_and_linkedlist/array.md | 130 +- ja/docs/chapter_array_and_linkedlist/index.md | 4 +- .../linked_list.md | 279 ++-- ja/docs/chapter_array_and_linkedlist/list.md | 390 ++++-- .../ram_and_cache.md | 78 +- .../chapter_array_and_linkedlist/summary.md | 93 +- .../backtracking_algorithm.md | 310 ++--- ja/docs/chapter_backtracking/index.md | 6 +- .../chapter_backtracking/n_queens_problem.md | 40 +- .../permutations_problem.md | 86 +- .../subset_sum_problem.md | 82 +- ja/docs/chapter_backtracking/summary.md | 30 +- .../chapter_computational_complexity/index.md | 10 +- .../iteration_and_recursion.md | 160 +-- .../performance_evaluation.md | 54 +- .../space_complexity.md | 417 +++--- .../summary.md | 60 +- .../time_complexity.md | 578 +++++---- .../basic_data_types.md | 104 +- .../character_encoding.md | 94 +- .../classification_of_data_structure.md | 42 +- ja/docs/chapter_data_structure/index.md | 8 +- .../chapter_data_structure/number_encoding.md | 116 +- ja/docs/chapter_data_structure/summary.md | 62 +- .../binary_search_recur.md | 46 +- .../build_binary_tree_problem.md | 80 +- .../divide_and_conquer.md | 88 +- .../hanota_problem.md | 74 +- ja/docs/chapter_divide_and_conquer/index.md | 6 +- ja/docs/chapter_divide_and_conquer/summary.md | 20 +- .../dp_problem_features.md | 80 +- .../dp_solution_pipeline.md | 118 +- .../edit_distance_problem.md | 68 +- ja/docs/chapter_dynamic_programming/index.md | 10 +- .../intro_to_dynamic_programming.md | 84 +- .../knapsack_problem.md | 92 +- .../chapter_dynamic_programming/summary.md | 32 +- .../unbounded_knapsack_problem.md | 100 +- ja/docs/chapter_graph/graph.md | 66 +- ja/docs/chapter_graph/graph_operations.md | 58 +- ja/docs/chapter_graph/graph_traversal.md | 68 +- ja/docs/chapter_graph/index.md | 6 +- ja/docs/chapter_graph/summary.md | 38 +- .../fractional_knapsack_problem.md | 36 +- ja/docs/chapter_greedy/greedy_algorithm.md | 98 +- ja/docs/chapter_greedy/index.md | 4 +- .../chapter_greedy/max_capacity_problem.md | 58 +- .../max_product_cutting_problem.md | 60 +- ja/docs/chapter_greedy/summary.md | 22 +- ja/docs/chapter_hashing/hash_algorithm.md | 276 ++-- ja/docs/chapter_hashing/hash_collision.md | 106 +- ja/docs/chapter_hashing/hash_map.md | 444 ++++--- ja/docs/chapter_hashing/index.md | 10 +- ja/docs/chapter_hashing/summary.md | 60 +- ja/docs/chapter_heap/build_heap.md | 50 +- ja/docs/chapter_heap/heap.md | 278 ++-- ja/docs/chapter_heap/index.md | 6 +- ja/docs/chapter_heap/summary.md | 20 +- ja/docs/chapter_heap/top_k.md | 44 +- ja/docs/chapter_hello_algo/index.md | 22 +- .../algorithms_are_everywhere.md | 58 +- ja/docs/chapter_introduction/index.md | 10 +- ja/docs/chapter_introduction/summary.md | 28 +- ja/docs/chapter_introduction/what_is_dsa.md | 58 +- ja/docs/chapter_paperbook/index.md | 68 + ja/docs/chapter_preface/about_the_book.md | 54 +- ja/docs/chapter_preface/index.md | 10 +- ja/docs/chapter_preface/suggestions.md | 175 +-- ja/docs/chapter_preface/summary.md | 14 +- ja/docs/chapter_reference/index.md | 10 +- ja/docs/chapter_searching/binary_search.md | 60 +- .../chapter_searching/binary_search_edge.md | 46 +- .../binary_search_insertion.md | 54 +- ja/docs/chapter_searching/index.md | 8 +- .../replace_linear_by_hashing.md | 32 +- .../searching_algorithm_revisited.md | 92 +- ja/docs/chapter_searching/summary.md | 14 +- ja/docs/chapter_sorting/bubble_sort.md | 34 +- ja/docs/chapter_sorting/bucket_sort.md | 44 +- ja/docs/chapter_sorting/counting_sort.md | 50 +- ja/docs/chapter_sorting/heap_sort.md | 34 +- ja/docs/chapter_sorting/index.md | 6 +- ja/docs/chapter_sorting/insertion_sort.md | 46 +- ja/docs/chapter_sorting/merge_sort.md | 42 +- ja/docs/chapter_sorting/quick_sort.md | 65 +- ja/docs/chapter_sorting/radix_sort.md | 34 +- ja/docs/chapter_sorting/selection_sort.md | 28 +- ja/docs/chapter_sorting/sorting_algorithm.md | 34 +- ja/docs/chapter_sorting/summary.md | 50 +- ja/docs/chapter_stack_and_queue/deque.md | 183 ++- ja/docs/chapter_stack_and_queue/index.md | 6 +- ja/docs/chapter_stack_and_queue/queue.md | 176 ++- ja/docs/chapter_stack_and_queue/stack.md | 219 ++-- ja/docs/chapter_stack_and_queue/summary.md | 34 +- .../array_representation_of_tree.md | 86 +- ja/docs/chapter_tree/avl_tree.md | 187 +-- ja/docs/chapter_tree/binary_search_tree.md | 92 +- ja/docs/chapter_tree/binary_tree.md | 324 ++--- ja/docs/chapter_tree/binary_tree_traversal.md | 46 +- ja/docs/chapter_tree/index.md | 6 +- ja/docs/chapter_tree/summary.md | 62 +- ja/docs/index.md | 4 +- ja/mkdocs.yml | 50 +- overrides/main.html | 2 +- 1444 files changed, 83312 insertions(+), 8363 deletions(-) delete mode 100644 ja/CONTRIBUTING.md create mode 100644 ja/codes/c/.gitignore create mode 100644 ja/codes/c/CMakeLists.txt create mode 100644 ja/codes/c/chapter_array_and_linkedlist/CMakeLists.txt create mode 100644 ja/codes/c/chapter_array_and_linkedlist/array.c create mode 100644 ja/codes/c/chapter_array_and_linkedlist/linked_list.c create mode 100644 ja/codes/c/chapter_array_and_linkedlist/my_list.c create mode 100644 ja/codes/c/chapter_backtracking/CMakeLists.txt create mode 100644 ja/codes/c/chapter_backtracking/n_queens.c create mode 100644 ja/codes/c/chapter_backtracking/permutations_i.c create mode 100644 ja/codes/c/chapter_backtracking/permutations_ii.c create mode 100644 ja/codes/c/chapter_backtracking/preorder_traversal_i_compact.c create mode 100644 ja/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c create mode 100644 ja/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c create mode 100644 ja/codes/c/chapter_backtracking/preorder_traversal_iii_template.c create mode 100644 ja/codes/c/chapter_backtracking/subset_sum_i.c create mode 100644 ja/codes/c/chapter_backtracking/subset_sum_i_naive.c create mode 100644 ja/codes/c/chapter_backtracking/subset_sum_ii.c create mode 100644 ja/codes/c/chapter_computational_complexity/CMakeLists.txt create mode 100644 ja/codes/c/chapter_computational_complexity/iteration.c create mode 100644 ja/codes/c/chapter_computational_complexity/recursion.c create mode 100644 ja/codes/c/chapter_computational_complexity/space_complexity.c create mode 100644 ja/codes/c/chapter_computational_complexity/time_complexity.c create mode 100644 ja/codes/c/chapter_computational_complexity/worst_best_time_complexity.c create mode 100644 ja/codes/c/chapter_divide_and_conquer/CMakeLists.txt create mode 100644 ja/codes/c/chapter_divide_and_conquer/binary_search_recur.c create mode 100644 ja/codes/c/chapter_divide_and_conquer/build_tree.c create mode 100644 ja/codes/c/chapter_divide_and_conquer/hanota.c create mode 100644 ja/codes/c/chapter_dynamic_programming/CMakeLists.txt create mode 100644 ja/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c create mode 100644 ja/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c create mode 100644 ja/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c create mode 100644 ja/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c create mode 100644 ja/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c create mode 100644 ja/codes/c/chapter_dynamic_programming/coin_change.c create mode 100644 ja/codes/c/chapter_dynamic_programming/coin_change_ii.c create mode 100644 ja/codes/c/chapter_dynamic_programming/edit_distance.c create mode 100644 ja/codes/c/chapter_dynamic_programming/knapsack.c create mode 100644 ja/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c create mode 100644 ja/codes/c/chapter_dynamic_programming/min_path_sum.c create mode 100644 ja/codes/c/chapter_dynamic_programming/unbounded_knapsack.c create mode 100644 ja/codes/c/chapter_graph/CMakeLists.txt create mode 100644 ja/codes/c/chapter_graph/graph_adjacency_list.c create mode 100644 ja/codes/c/chapter_graph/graph_adjacency_list_test.c create mode 100644 ja/codes/c/chapter_graph/graph_adjacency_matrix.c create mode 100644 ja/codes/c/chapter_graph/graph_bfs.c create mode 100644 ja/codes/c/chapter_graph/graph_dfs.c create mode 100644 ja/codes/c/chapter_greedy/CMakeLists.txt create mode 100644 ja/codes/c/chapter_greedy/coin_change_greedy.c create mode 100644 ja/codes/c/chapter_greedy/fractional_knapsack.c create mode 100644 ja/codes/c/chapter_greedy/max_capacity.c create mode 100644 ja/codes/c/chapter_greedy/max_product_cutting.c create mode 100644 ja/codes/c/chapter_hashing/CMakeLists.txt create mode 100644 ja/codes/c/chapter_hashing/array_hash_map.c create mode 100644 ja/codes/c/chapter_hashing/hash_map_chaining.c create mode 100644 ja/codes/c/chapter_hashing/hash_map_open_addressing.c create mode 100644 ja/codes/c/chapter_hashing/simple_hash.c create mode 100644 ja/codes/c/chapter_heap/CMakeLists.txt create mode 100644 ja/codes/c/chapter_heap/my_heap.c create mode 100644 ja/codes/c/chapter_heap/my_heap_test.c create mode 100644 ja/codes/c/chapter_heap/top_k.c create mode 100644 ja/codes/c/chapter_searching/CMakeLists.txt create mode 100644 ja/codes/c/chapter_searching/binary_search.c create mode 100644 ja/codes/c/chapter_searching/binary_search_edge.c create mode 100644 ja/codes/c/chapter_searching/binary_search_insertion.c create mode 100644 ja/codes/c/chapter_searching/two_sum.c create mode 100644 ja/codes/c/chapter_sorting/CMakeLists.txt create mode 100644 ja/codes/c/chapter_sorting/bubble_sort.c create mode 100644 ja/codes/c/chapter_sorting/bucket_sort.c create mode 100644 ja/codes/c/chapter_sorting/counting_sort.c create mode 100644 ja/codes/c/chapter_sorting/heap_sort.c create mode 100644 ja/codes/c/chapter_sorting/insertion_sort.c create mode 100644 ja/codes/c/chapter_sorting/merge_sort.c create mode 100644 ja/codes/c/chapter_sorting/quick_sort.c create mode 100644 ja/codes/c/chapter_sorting/radix_sort.c create mode 100644 ja/codes/c/chapter_sorting/selection_sort.c create mode 100644 ja/codes/c/chapter_stack_and_queue/CMakeLists.txt create mode 100644 ja/codes/c/chapter_stack_and_queue/array_deque.c create mode 100644 ja/codes/c/chapter_stack_and_queue/array_queue.c create mode 100644 ja/codes/c/chapter_stack_and_queue/array_stack.c create mode 100644 ja/codes/c/chapter_stack_and_queue/linkedlist_deque.c create mode 100644 ja/codes/c/chapter_stack_and_queue/linkedlist_queue.c create mode 100644 ja/codes/c/chapter_stack_and_queue/linkedlist_stack.c create mode 100644 ja/codes/c/chapter_tree/CMakeLists.txt create mode 100644 ja/codes/c/chapter_tree/array_binary_tree.c create mode 100644 ja/codes/c/chapter_tree/avl_tree.c create mode 100644 ja/codes/c/chapter_tree/binary_search_tree.c create mode 100644 ja/codes/c/chapter_tree/binary_tree.c create mode 100644 ja/codes/c/chapter_tree/binary_tree_bfs.c create mode 100644 ja/codes/c/chapter_tree/binary_tree_dfs.c create mode 100644 ja/codes/c/utils/CMakeLists.txt create mode 100644 ja/codes/c/utils/common.h create mode 100644 ja/codes/c/utils/common_test.c create mode 100644 ja/codes/c/utils/list_node.h create mode 100644 ja/codes/c/utils/print_util.h create mode 100644 ja/codes/c/utils/tree_node.h create mode 100644 ja/codes/c/utils/uthash.h create mode 100644 ja/codes/c/utils/vector.h create mode 100644 ja/codes/c/utils/vertex.h create mode 100644 ja/codes/cpp/.gitignore create mode 100644 ja/codes/cpp/CMakeLists.txt create mode 100644 ja/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt create mode 100644 ja/codes/cpp/chapter_backtracking/CMakeLists.txt create mode 100644 ja/codes/cpp/chapter_computational_complexity/CMakeLists.txt create mode 100644 ja/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt create mode 100644 ja/codes/cpp/chapter_dynamic_programming/CMakeLists.txt create mode 100644 ja/codes/cpp/chapter_graph/CMakeLists.txt create mode 100644 ja/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp create mode 100644 ja/codes/cpp/chapter_greedy/CMakeLists.txt create mode 100644 ja/codes/cpp/chapter_hashing/CMakeLists.txt create mode 100644 ja/codes/cpp/chapter_heap/CMakeLists.txt create mode 100644 ja/codes/cpp/chapter_searching/CMakeLists.txt create mode 100644 ja/codes/cpp/chapter_sorting/CMakeLists.txt create mode 100644 ja/codes/cpp/chapter_stack_and_queue/CMakeLists.txt create mode 100644 ja/codes/cpp/chapter_tree/CMakeLists.txt create mode 100644 ja/codes/cpp/utils/CMakeLists.txt create mode 100644 ja/codes/csharp/.editorconfig create mode 100644 ja/codes/csharp/.gitignore create mode 100644 ja/codes/csharp/GlobalUsing.cs create mode 100644 ja/codes/csharp/chapter_array_and_linkedlist/array.cs create mode 100644 ja/codes/csharp/chapter_array_and_linkedlist/linked_list.cs create mode 100644 ja/codes/csharp/chapter_array_and_linkedlist/list.cs create mode 100644 ja/codes/csharp/chapter_array_and_linkedlist/my_list.cs create mode 100644 ja/codes/csharp/chapter_backtracking/n_queens.cs create mode 100644 ja/codes/csharp/chapter_backtracking/permutations_i.cs create mode 100644 ja/codes/csharp/chapter_backtracking/permutations_ii.cs create mode 100644 ja/codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs create mode 100644 ja/codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs create mode 100644 ja/codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs create mode 100644 ja/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs create mode 100644 ja/codes/csharp/chapter_backtracking/subset_sum_i.cs create mode 100644 ja/codes/csharp/chapter_backtracking/subset_sum_i_naive.cs create mode 100644 ja/codes/csharp/chapter_backtracking/subset_sum_ii.cs create mode 100644 ja/codes/csharp/chapter_computational_complexity/iteration.cs create mode 100644 ja/codes/csharp/chapter_computational_complexity/recursion.cs create mode 100644 ja/codes/csharp/chapter_computational_complexity/space_complexity.cs create mode 100644 ja/codes/csharp/chapter_computational_complexity/time_complexity.cs create mode 100644 ja/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs create mode 100644 ja/codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs create mode 100644 ja/codes/csharp/chapter_divide_and_conquer/build_tree.cs create mode 100644 ja/codes/csharp/chapter_divide_and_conquer/hanota.cs create mode 100644 ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs create mode 100644 ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs create mode 100644 ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs create mode 100644 ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs create mode 100644 ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs create mode 100644 ja/codes/csharp/chapter_dynamic_programming/coin_change.cs create mode 100644 ja/codes/csharp/chapter_dynamic_programming/coin_change_ii.cs create mode 100644 ja/codes/csharp/chapter_dynamic_programming/edit_distance.cs create mode 100644 ja/codes/csharp/chapter_dynamic_programming/knapsack.cs create mode 100644 ja/codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs create mode 100644 ja/codes/csharp/chapter_dynamic_programming/min_path_sum.cs create mode 100644 ja/codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs create mode 100644 ja/codes/csharp/chapter_graph/graph_adjacency_list.cs create mode 100644 ja/codes/csharp/chapter_graph/graph_adjacency_matrix.cs create mode 100644 ja/codes/csharp/chapter_graph/graph_bfs.cs create mode 100644 ja/codes/csharp/chapter_graph/graph_dfs.cs create mode 100644 ja/codes/csharp/chapter_greedy/coin_change_greedy.cs create mode 100644 ja/codes/csharp/chapter_greedy/fractional_knapsack.cs create mode 100644 ja/codes/csharp/chapter_greedy/max_capacity.cs create mode 100644 ja/codes/csharp/chapter_greedy/max_product_cutting.cs create mode 100644 ja/codes/csharp/chapter_hashing/array_hash_map.cs create mode 100644 ja/codes/csharp/chapter_hashing/built_in_hash.cs create mode 100644 ja/codes/csharp/chapter_hashing/hash_map.cs create mode 100644 ja/codes/csharp/chapter_hashing/hash_map_chaining.cs create mode 100644 ja/codes/csharp/chapter_hashing/hash_map_open_addressing.cs create mode 100644 ja/codes/csharp/chapter_hashing/simple_hash.cs create mode 100644 ja/codes/csharp/chapter_heap/heap.cs create mode 100644 ja/codes/csharp/chapter_heap/my_heap.cs create mode 100644 ja/codes/csharp/chapter_heap/top_k.cs create mode 100644 ja/codes/csharp/chapter_searching/binary_search.cs create mode 100644 ja/codes/csharp/chapter_searching/binary_search_edge.cs create mode 100644 ja/codes/csharp/chapter_searching/binary_search_insertion.cs create mode 100644 ja/codes/csharp/chapter_searching/hashing_search.cs create mode 100644 ja/codes/csharp/chapter_searching/linear_search.cs create mode 100644 ja/codes/csharp/chapter_searching/two_sum.cs create mode 100644 ja/codes/csharp/chapter_sorting/bubble_sort.cs create mode 100644 ja/codes/csharp/chapter_sorting/bucket_sort.cs create mode 100644 ja/codes/csharp/chapter_sorting/counting_sort.cs create mode 100644 ja/codes/csharp/chapter_sorting/heap_sort.cs create mode 100644 ja/codes/csharp/chapter_sorting/insertion_sort.cs create mode 100644 ja/codes/csharp/chapter_sorting/merge_sort.cs create mode 100644 ja/codes/csharp/chapter_sorting/quick_sort.cs create mode 100644 ja/codes/csharp/chapter_sorting/radix_sort.cs create mode 100644 ja/codes/csharp/chapter_sorting/selection_sort.cs create mode 100644 ja/codes/csharp/chapter_stack_and_queue/array_deque.cs create mode 100644 ja/codes/csharp/chapter_stack_and_queue/array_queue.cs create mode 100644 ja/codes/csharp/chapter_stack_and_queue/array_stack.cs create mode 100644 ja/codes/csharp/chapter_stack_and_queue/deque.cs create mode 100644 ja/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs create mode 100644 ja/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs create mode 100644 ja/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs create mode 100644 ja/codes/csharp/chapter_stack_and_queue/queue.cs create mode 100644 ja/codes/csharp/chapter_stack_and_queue/stack.cs create mode 100644 ja/codes/csharp/chapter_tree/array_binary_tree.cs create mode 100644 ja/codes/csharp/chapter_tree/avl_tree.cs create mode 100644 ja/codes/csharp/chapter_tree/binary_search_tree.cs create mode 100644 ja/codes/csharp/chapter_tree/binary_tree.cs create mode 100644 ja/codes/csharp/chapter_tree/binary_tree_bfs.cs create mode 100644 ja/codes/csharp/chapter_tree/binary_tree_dfs.cs create mode 100644 ja/codes/csharp/csharp.sln create mode 100644 ja/codes/csharp/hello-algo.csproj create mode 100644 ja/codes/csharp/utils/ListNode.cs create mode 100644 ja/codes/csharp/utils/PrintUtil.cs create mode 100644 ja/codes/csharp/utils/TreeNode.cs create mode 100644 ja/codes/csharp/utils/Vertex.cs create mode 100644 ja/codes/dart/build.dart create mode 100644 ja/codes/dart/chapter_array_and_linkedlist/array.dart create mode 100644 ja/codes/dart/chapter_array_and_linkedlist/linked_list.dart create mode 100644 ja/codes/dart/chapter_array_and_linkedlist/list.dart create mode 100644 ja/codes/dart/chapter_array_and_linkedlist/my_list.dart create mode 100644 ja/codes/dart/chapter_backtracking/n_queens.dart create mode 100644 ja/codes/dart/chapter_backtracking/permutations_i.dart create mode 100644 ja/codes/dart/chapter_backtracking/permutations_ii.dart create mode 100644 ja/codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart create mode 100644 ja/codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart create mode 100644 ja/codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart create mode 100644 ja/codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart create mode 100644 ja/codes/dart/chapter_backtracking/subset_sum_i.dart create mode 100644 ja/codes/dart/chapter_backtracking/subset_sum_i_naive.dart create mode 100644 ja/codes/dart/chapter_backtracking/subset_sum_ii.dart create mode 100644 ja/codes/dart/chapter_computational_complexity/iteration.dart create mode 100644 ja/codes/dart/chapter_computational_complexity/recursion.dart create mode 100644 ja/codes/dart/chapter_computational_complexity/space_complexity.dart create mode 100644 ja/codes/dart/chapter_computational_complexity/time_complexity.dart create mode 100644 ja/codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart create mode 100644 ja/codes/dart/chapter_divide_and_conquer/binary_search_recur.dart create mode 100644 ja/codes/dart/chapter_divide_and_conquer/build_tree.dart create mode 100644 ja/codes/dart/chapter_divide_and_conquer/hanota.dart create mode 100644 ja/codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart create mode 100644 ja/codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart create mode 100644 ja/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart create mode 100644 ja/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart create mode 100644 ja/codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart create mode 100644 ja/codes/dart/chapter_dynamic_programming/coin_change.dart create mode 100644 ja/codes/dart/chapter_dynamic_programming/coin_change_ii.dart create mode 100644 ja/codes/dart/chapter_dynamic_programming/edit_distance.dart create mode 100644 ja/codes/dart/chapter_dynamic_programming/knapsack.dart create mode 100644 ja/codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart create mode 100644 ja/codes/dart/chapter_dynamic_programming/min_path_sum.dart create mode 100644 ja/codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart create mode 100644 ja/codes/dart/chapter_graph/graph_adjacency_list.dart create mode 100644 ja/codes/dart/chapter_graph/graph_adjacency_matrix.dart create mode 100644 ja/codes/dart/chapter_graph/graph_bfs.dart create mode 100644 ja/codes/dart/chapter_graph/graph_dfs.dart create mode 100644 ja/codes/dart/chapter_greedy/coin_change_greedy.dart create mode 100644 ja/codes/dart/chapter_greedy/fractional_knapsack.dart create mode 100644 ja/codes/dart/chapter_greedy/max_capacity.dart create mode 100644 ja/codes/dart/chapter_greedy/max_product_cutting.dart create mode 100644 ja/codes/dart/chapter_hashing/array_hash_map.dart create mode 100644 ja/codes/dart/chapter_hashing/built_in_hash.dart create mode 100644 ja/codes/dart/chapter_hashing/hash_map.dart create mode 100644 ja/codes/dart/chapter_hashing/hash_map_chaining.dart create mode 100644 ja/codes/dart/chapter_hashing/hash_map_open_addressing.dart create mode 100644 ja/codes/dart/chapter_hashing/simple_hash.dart create mode 100644 ja/codes/dart/chapter_heap/my_heap.dart create mode 100644 ja/codes/dart/chapter_heap/top_k.dart create mode 100644 ja/codes/dart/chapter_searching/binary_search.dart create mode 100644 ja/codes/dart/chapter_searching/binary_search_edge.dart create mode 100644 ja/codes/dart/chapter_searching/binary_search_insertion.dart create mode 100644 ja/codes/dart/chapter_searching/hashing_search.dart create mode 100644 ja/codes/dart/chapter_searching/linear_search.dart create mode 100644 ja/codes/dart/chapter_searching/two_sum.dart create mode 100644 ja/codes/dart/chapter_sorting/bubble_sort.dart create mode 100644 ja/codes/dart/chapter_sorting/bucket_sort.dart create mode 100644 ja/codes/dart/chapter_sorting/counting_sort.dart create mode 100644 ja/codes/dart/chapter_sorting/heap_sort.dart create mode 100644 ja/codes/dart/chapter_sorting/insertion_sort.dart create mode 100644 ja/codes/dart/chapter_sorting/merge_sort.dart create mode 100644 ja/codes/dart/chapter_sorting/quick_sort.dart create mode 100644 ja/codes/dart/chapter_sorting/radix_sort.dart create mode 100644 ja/codes/dart/chapter_sorting/selection_sort.dart create mode 100644 ja/codes/dart/chapter_stack_and_queue/array_deque.dart create mode 100644 ja/codes/dart/chapter_stack_and_queue/array_queue.dart create mode 100644 ja/codes/dart/chapter_stack_and_queue/array_stack.dart create mode 100644 ja/codes/dart/chapter_stack_and_queue/deque.dart create mode 100644 ja/codes/dart/chapter_stack_and_queue/linkedlist_deque.dart create mode 100644 ja/codes/dart/chapter_stack_and_queue/linkedlist_queue.dart create mode 100644 ja/codes/dart/chapter_stack_and_queue/linkedlist_stack.dart create mode 100644 ja/codes/dart/chapter_stack_and_queue/queue.dart create mode 100644 ja/codes/dart/chapter_stack_and_queue/stack.dart create mode 100644 ja/codes/dart/chapter_tree/array_binary_tree.dart create mode 100644 ja/codes/dart/chapter_tree/avl_tree.dart create mode 100644 ja/codes/dart/chapter_tree/binary_search_tree.dart create mode 100644 ja/codes/dart/chapter_tree/binary_tree.dart create mode 100644 ja/codes/dart/chapter_tree/binary_tree_bfs.dart create mode 100644 ja/codes/dart/chapter_tree/binary_tree_dfs.dart create mode 100644 ja/codes/dart/utils/list_node.dart create mode 100644 ja/codes/dart/utils/print_util.dart create mode 100644 ja/codes/dart/utils/tree_node.dart create mode 100644 ja/codes/dart/utils/vertex.dart create mode 100644 ja/codes/go/chapter_array_and_linkedlist/array.go create mode 100644 ja/codes/go/chapter_array_and_linkedlist/array_test.go create mode 100644 ja/codes/go/chapter_array_and_linkedlist/linked_list.go create mode 100644 ja/codes/go/chapter_array_and_linkedlist/linked_list_test.go create mode 100644 ja/codes/go/chapter_array_and_linkedlist/list_test.go create mode 100644 ja/codes/go/chapter_array_and_linkedlist/my_list.go create mode 100644 ja/codes/go/chapter_array_and_linkedlist/my_list_test.go create mode 100644 ja/codes/go/chapter_backtracking/n_queens.go create mode 100644 ja/codes/go/chapter_backtracking/n_queens_test.go create mode 100644 ja/codes/go/chapter_backtracking/permutation_test.go create mode 100644 ja/codes/go/chapter_backtracking/permutations_i.go create mode 100644 ja/codes/go/chapter_backtracking/permutations_ii.go create mode 100644 ja/codes/go/chapter_backtracking/preorder_traversal_i_compact.go create mode 100644 ja/codes/go/chapter_backtracking/preorder_traversal_ii_compact.go create mode 100644 ja/codes/go/chapter_backtracking/preorder_traversal_iii_compact.go create mode 100644 ja/codes/go/chapter_backtracking/preorder_traversal_iii_template.go create mode 100644 ja/codes/go/chapter_backtracking/preorder_traversal_test.go create mode 100644 ja/codes/go/chapter_backtracking/subset_sum_i.go create mode 100644 ja/codes/go/chapter_backtracking/subset_sum_i_naive.go create mode 100644 ja/codes/go/chapter_backtracking/subset_sum_ii.go create mode 100644 ja/codes/go/chapter_backtracking/subset_sum_test.go create mode 100644 ja/codes/go/chapter_computational_complexity/iteration.go create mode 100644 ja/codes/go/chapter_computational_complexity/iteration_test.go create mode 100644 ja/codes/go/chapter_computational_complexity/recursion.go create mode 100644 ja/codes/go/chapter_computational_complexity/recursion_test.go create mode 100644 ja/codes/go/chapter_computational_complexity/space_complexity.go create mode 100644 ja/codes/go/chapter_computational_complexity/space_complexity_test.go create mode 100644 ja/codes/go/chapter_computational_complexity/time_complexity.go create mode 100644 ja/codes/go/chapter_computational_complexity/time_complexity_test.go create mode 100644 ja/codes/go/chapter_computational_complexity/worst_best_time_complexity.go create mode 100644 ja/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go create mode 100644 ja/codes/go/chapter_divide_and_conquer/binary_search_recur.go create mode 100644 ja/codes/go/chapter_divide_and_conquer/binary_search_recur_test.go create mode 100644 ja/codes/go/chapter_divide_and_conquer/build_tree.go create mode 100644 ja/codes/go/chapter_divide_and_conquer/build_tree_test.go create mode 100644 ja/codes/go/chapter_divide_and_conquer/hanota.go create mode 100644 ja/codes/go/chapter_divide_and_conquer/hanota_test.go create mode 100644 ja/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go create mode 100644 ja/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go create mode 100644 ja/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go create mode 100644 ja/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go create mode 100644 ja/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go create mode 100644 ja/codes/go/chapter_dynamic_programming/climbing_stairs_test.go create mode 100644 ja/codes/go/chapter_dynamic_programming/coin_change.go create mode 100644 ja/codes/go/chapter_dynamic_programming/coin_change_ii.go create mode 100644 ja/codes/go/chapter_dynamic_programming/coin_change_test.go create mode 100644 ja/codes/go/chapter_dynamic_programming/edit_distance.go create mode 100644 ja/codes/go/chapter_dynamic_programming/edit_distance_test.go create mode 100644 ja/codes/go/chapter_dynamic_programming/knapsack.go create mode 100644 ja/codes/go/chapter_dynamic_programming/knapsack_test.go create mode 100644 ja/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go create mode 100644 ja/codes/go/chapter_dynamic_programming/min_path_sum.go create mode 100644 ja/codes/go/chapter_dynamic_programming/min_path_sum_test.go create mode 100644 ja/codes/go/chapter_dynamic_programming/unbounded_knapsack.go create mode 100644 ja/codes/go/chapter_graph/graph_adjacency_list.go create mode 100644 ja/codes/go/chapter_graph/graph_adjacency_list_test.go create mode 100644 ja/codes/go/chapter_graph/graph_adjacency_matrix.go create mode 100644 ja/codes/go/chapter_graph/graph_adjacency_matrix_test.go create mode 100644 ja/codes/go/chapter_graph/graph_bfs.go create mode 100644 ja/codes/go/chapter_graph/graph_bfs_test.go create mode 100644 ja/codes/go/chapter_graph/graph_dfs.go create mode 100644 ja/codes/go/chapter_graph/graph_dfs_test.go create mode 100644 ja/codes/go/chapter_greedy/coin_change_greedy.go create mode 100644 ja/codes/go/chapter_greedy/coin_change_greedy_test.go create mode 100644 ja/codes/go/chapter_greedy/fractional_knapsack.go create mode 100644 ja/codes/go/chapter_greedy/fractional_knapsack_test.go create mode 100644 ja/codes/go/chapter_greedy/max_capacity.go create mode 100644 ja/codes/go/chapter_greedy/max_capacity_test.go create mode 100644 ja/codes/go/chapter_greedy/max_product_cutting.go create mode 100644 ja/codes/go/chapter_greedy/max_product_cutting_test.go create mode 100644 ja/codes/go/chapter_hashing/array_hash_map.go create mode 100644 ja/codes/go/chapter_hashing/array_hash_map_test.go create mode 100644 ja/codes/go/chapter_hashing/hash_collision_test.go create mode 100644 ja/codes/go/chapter_hashing/hash_map_chaining.go create mode 100644 ja/codes/go/chapter_hashing/hash_map_open_addressing.go create mode 100644 ja/codes/go/chapter_hashing/hash_map_test.go create mode 100644 ja/codes/go/chapter_hashing/simple_hash.go create mode 100644 ja/codes/go/chapter_heap/heap.go create mode 100644 ja/codes/go/chapter_heap/heap_test.go create mode 100644 ja/codes/go/chapter_heap/my_heap.go create mode 100644 ja/codes/go/chapter_heap/top_k.go create mode 100644 ja/codes/go/chapter_searching/binary_search.go create mode 100644 ja/codes/go/chapter_searching/binary_search_edge.go create mode 100644 ja/codes/go/chapter_searching/binary_search_insertion.go create mode 100644 ja/codes/go/chapter_searching/binary_search_test.go create mode 100644 ja/codes/go/chapter_searching/hashing_search.go create mode 100644 ja/codes/go/chapter_searching/hashing_search_test.go create mode 100644 ja/codes/go/chapter_searching/linear_search.go create mode 100644 ja/codes/go/chapter_searching/linear_search_test.go create mode 100644 ja/codes/go/chapter_searching/two_sum.go create mode 100644 ja/codes/go/chapter_searching/two_sum_test.go create mode 100644 ja/codes/go/chapter_sorting/bubble_sort.go create mode 100644 ja/codes/go/chapter_sorting/bubble_sort_test.go create mode 100644 ja/codes/go/chapter_sorting/bucket_sort.go create mode 100644 ja/codes/go/chapter_sorting/bucket_sort_test.go create mode 100644 ja/codes/go/chapter_sorting/counting_sort.go create mode 100644 ja/codes/go/chapter_sorting/counting_sort_test.go create mode 100644 ja/codes/go/chapter_sorting/heap_sort.go create mode 100644 ja/codes/go/chapter_sorting/heap_sort_test.go create mode 100644 ja/codes/go/chapter_sorting/insertion_sort.go create mode 100644 ja/codes/go/chapter_sorting/insertion_sort_test.go create mode 100644 ja/codes/go/chapter_sorting/merge_sort.go create mode 100644 ja/codes/go/chapter_sorting/merge_sort_test.go create mode 100644 ja/codes/go/chapter_sorting/quick_sort.go create mode 100644 ja/codes/go/chapter_sorting/quick_sort_test.go create mode 100644 ja/codes/go/chapter_sorting/radix_sort.go create mode 100644 ja/codes/go/chapter_sorting/radix_sort_test.go create mode 100644 ja/codes/go/chapter_sorting/selection_sort.go create mode 100644 ja/codes/go/chapter_sorting/selection_sort_test.go create mode 100644 ja/codes/go/chapter_stack_and_queue/array_deque.go create mode 100644 ja/codes/go/chapter_stack_and_queue/array_queue.go create mode 100644 ja/codes/go/chapter_stack_and_queue/array_stack.go create mode 100644 ja/codes/go/chapter_stack_and_queue/deque_test.go create mode 100644 ja/codes/go/chapter_stack_and_queue/linkedlist_deque.go create mode 100644 ja/codes/go/chapter_stack_and_queue/linkedlist_queue.go create mode 100644 ja/codes/go/chapter_stack_and_queue/linkedlist_stack.go create mode 100644 ja/codes/go/chapter_stack_and_queue/queue_test.go create mode 100644 ja/codes/go/chapter_stack_and_queue/stack_test.go create mode 100644 ja/codes/go/chapter_tree/array_binary_tree.go create mode 100644 ja/codes/go/chapter_tree/array_binary_tree_test.go create mode 100644 ja/codes/go/chapter_tree/avl_tree.go create mode 100644 ja/codes/go/chapter_tree/avl_tree_test.go create mode 100644 ja/codes/go/chapter_tree/binary_search_tree.go create mode 100644 ja/codes/go/chapter_tree/binary_search_tree_test.go create mode 100644 ja/codes/go/chapter_tree/binary_tree_bfs.go create mode 100644 ja/codes/go/chapter_tree/binary_tree_bfs_test.go create mode 100644 ja/codes/go/chapter_tree/binary_tree_dfs.go create mode 100644 ja/codes/go/chapter_tree/binary_tree_dfs_test.go create mode 100644 ja/codes/go/chapter_tree/binary_tree_test.go create mode 100644 ja/codes/go/go.mod create mode 100644 ja/codes/go/pkg/list_node.go create mode 100644 ja/codes/go/pkg/list_node_test.go create mode 100644 ja/codes/go/pkg/print_utils.go create mode 100644 ja/codes/go/pkg/tree_node.go create mode 100644 ja/codes/go/pkg/tree_node_test.go create mode 100644 ja/codes/go/pkg/vertex.go create mode 100644 ja/codes/java/.gitignore create mode 100644 ja/codes/javascript/.prettierrc create mode 100644 ja/codes/javascript/chapter_array_and_linkedlist/array.js create mode 100644 ja/codes/javascript/chapter_array_and_linkedlist/linked_list.js create mode 100644 ja/codes/javascript/chapter_array_and_linkedlist/list.js create mode 100644 ja/codes/javascript/chapter_array_and_linkedlist/my_list.js create mode 100644 ja/codes/javascript/chapter_backtracking/n_queens.js create mode 100644 ja/codes/javascript/chapter_backtracking/permutations_i.js create mode 100644 ja/codes/javascript/chapter_backtracking/permutations_ii.js create mode 100644 ja/codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js create mode 100644 ja/codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js create mode 100644 ja/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js create mode 100644 ja/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js create mode 100644 ja/codes/javascript/chapter_backtracking/subset_sum_i.js create mode 100644 ja/codes/javascript/chapter_backtracking/subset_sum_i_naive.js create mode 100644 ja/codes/javascript/chapter_backtracking/subset_sum_ii.js create mode 100644 ja/codes/javascript/chapter_computational_complexity/iteration.js create mode 100644 ja/codes/javascript/chapter_computational_complexity/recursion.js create mode 100644 ja/codes/javascript/chapter_computational_complexity/space_complexity.js create mode 100644 ja/codes/javascript/chapter_computational_complexity/time_complexity.js create mode 100644 ja/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js create mode 100644 ja/codes/javascript/chapter_divide_and_conquer/binary_search_recur.js create mode 100644 ja/codes/javascript/chapter_divide_and_conquer/build_tree.js create mode 100644 ja/codes/javascript/chapter_divide_and_conquer/hanota.js create mode 100644 ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js create mode 100644 ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js create mode 100644 ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js create mode 100644 ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js create mode 100644 ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js create mode 100644 ja/codes/javascript/chapter_dynamic_programming/coin_change.js create mode 100644 ja/codes/javascript/chapter_dynamic_programming/coin_change_ii.js create mode 100644 ja/codes/javascript/chapter_dynamic_programming/edit_distance.js create mode 100644 ja/codes/javascript/chapter_dynamic_programming/knapsack.js create mode 100644 ja/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js create mode 100644 ja/codes/javascript/chapter_dynamic_programming/min_path_sum.js create mode 100644 ja/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js create mode 100644 ja/codes/javascript/chapter_graph/graph_adjacency_list.js create mode 100644 ja/codes/javascript/chapter_graph/graph_adjacency_matrix.js create mode 100644 ja/codes/javascript/chapter_graph/graph_bfs.js create mode 100644 ja/codes/javascript/chapter_graph/graph_dfs.js create mode 100644 ja/codes/javascript/chapter_greedy/coin_change_greedy.js create mode 100644 ja/codes/javascript/chapter_greedy/fractional_knapsack.js create mode 100644 ja/codes/javascript/chapter_greedy/max_capacity.js create mode 100644 ja/codes/javascript/chapter_greedy/max_product_cutting.js create mode 100644 ja/codes/javascript/chapter_hashing/array_hash_map.js create mode 100644 ja/codes/javascript/chapter_hashing/hash_map.js create mode 100644 ja/codes/javascript/chapter_hashing/hash_map_chaining.js create mode 100644 ja/codes/javascript/chapter_hashing/hash_map_open_addressing.js create mode 100644 ja/codes/javascript/chapter_hashing/simple_hash.js create mode 100644 ja/codes/javascript/chapter_heap/my_heap.js create mode 100644 ja/codes/javascript/chapter_heap/top_k.js create mode 100644 ja/codes/javascript/chapter_searching/binary_search.js create mode 100644 ja/codes/javascript/chapter_searching/binary_search_edge.js create mode 100644 ja/codes/javascript/chapter_searching/binary_search_insertion.js create mode 100644 ja/codes/javascript/chapter_searching/hashing_search.js create mode 100644 ja/codes/javascript/chapter_searching/linear_search.js create mode 100644 ja/codes/javascript/chapter_searching/two_sum.js create mode 100644 ja/codes/javascript/chapter_sorting/bubble_sort.js create mode 100644 ja/codes/javascript/chapter_sorting/bucket_sort.js create mode 100644 ja/codes/javascript/chapter_sorting/counting_sort.js create mode 100644 ja/codes/javascript/chapter_sorting/heap_sort.js create mode 100644 ja/codes/javascript/chapter_sorting/insertion_sort.js create mode 100644 ja/codes/javascript/chapter_sorting/merge_sort.js create mode 100644 ja/codes/javascript/chapter_sorting/quick_sort.js create mode 100644 ja/codes/javascript/chapter_sorting/radix_sort.js create mode 100644 ja/codes/javascript/chapter_sorting/selection_sort.js create mode 100644 ja/codes/javascript/chapter_stack_and_queue/array_deque.js create mode 100644 ja/codes/javascript/chapter_stack_and_queue/array_queue.js create mode 100644 ja/codes/javascript/chapter_stack_and_queue/array_stack.js create mode 100644 ja/codes/javascript/chapter_stack_and_queue/deque.js create mode 100644 ja/codes/javascript/chapter_stack_and_queue/linkedlist_deque.js create mode 100644 ja/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js create mode 100644 ja/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js create mode 100644 ja/codes/javascript/chapter_stack_and_queue/queue.js create mode 100644 ja/codes/javascript/chapter_stack_and_queue/stack.js create mode 100644 ja/codes/javascript/chapter_tree/array_binary_tree.js create mode 100644 ja/codes/javascript/chapter_tree/avl_tree.js create mode 100644 ja/codes/javascript/chapter_tree/binary_search_tree.js create mode 100644 ja/codes/javascript/chapter_tree/binary_tree.js create mode 100644 ja/codes/javascript/chapter_tree/binary_tree_bfs.js create mode 100644 ja/codes/javascript/chapter_tree/binary_tree_dfs.js create mode 100644 ja/codes/javascript/modules/ListNode.js create mode 100644 ja/codes/javascript/modules/PrintUtil.js create mode 100644 ja/codes/javascript/modules/TreeNode.js create mode 100644 ja/codes/javascript/modules/Vertex.js create mode 100644 ja/codes/javascript/test_all.js create mode 100644 ja/codes/kotlin/chapter_array_and_linkedlist/array.kt create mode 100644 ja/codes/kotlin/chapter_array_and_linkedlist/linked_list.kt create mode 100644 ja/codes/kotlin/chapter_array_and_linkedlist/list.kt create mode 100644 ja/codes/kotlin/chapter_array_and_linkedlist/my_list.kt create mode 100644 ja/codes/kotlin/chapter_backtracking/n_queens.kt create mode 100644 ja/codes/kotlin/chapter_backtracking/permutations_i.kt create mode 100644 ja/codes/kotlin/chapter_backtracking/permutations_ii.kt create mode 100644 ja/codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt create mode 100644 ja/codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt create mode 100644 ja/codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt create mode 100644 ja/codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt create mode 100644 ja/codes/kotlin/chapter_backtracking/subset_sum_i.kt create mode 100644 ja/codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt create mode 100644 ja/codes/kotlin/chapter_backtracking/subset_sum_ii.kt create mode 100644 ja/codes/kotlin/chapter_computational_complexity/iteration.kt create mode 100644 ja/codes/kotlin/chapter_computational_complexity/recursion.kt create mode 100644 ja/codes/kotlin/chapter_computational_complexity/space_complexity.kt create mode 100644 ja/codes/kotlin/chapter_computational_complexity/time_complexity.kt create mode 100644 ja/codes/kotlin/chapter_computational_complexity/worst_best_time_complexity.kt create mode 100644 ja/codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt create mode 100644 ja/codes/kotlin/chapter_divide_and_conquer/build_tree.kt create mode 100644 ja/codes/kotlin/chapter_divide_and_conquer/hanota.kt create mode 100644 ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt create mode 100644 ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt create mode 100644 ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt create mode 100644 ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt create mode 100644 ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt create mode 100644 ja/codes/kotlin/chapter_dynamic_programming/coin_change.kt create mode 100644 ja/codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt create mode 100644 ja/codes/kotlin/chapter_dynamic_programming/edit_distance.kt create mode 100644 ja/codes/kotlin/chapter_dynamic_programming/knapsack.kt create mode 100644 ja/codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt create mode 100644 ja/codes/kotlin/chapter_dynamic_programming/min_path_sum.kt create mode 100644 ja/codes/kotlin/chapter_dynamic_programming/unbounded_knapsack.kt create mode 100644 ja/codes/kotlin/chapter_graph/graph_adjacency_list.kt create mode 100644 ja/codes/kotlin/chapter_graph/graph_adjacency_matrix.kt create mode 100644 ja/codes/kotlin/chapter_graph/graph_bfs.kt create mode 100644 ja/codes/kotlin/chapter_graph/graph_dfs.kt create mode 100644 ja/codes/kotlin/chapter_greedy/coin_change_greedy.kt create mode 100644 ja/codes/kotlin/chapter_greedy/fractional_knapsack.kt create mode 100644 ja/codes/kotlin/chapter_greedy/max_capacity.kt create mode 100644 ja/codes/kotlin/chapter_greedy/max_product_cutting.kt create mode 100644 ja/codes/kotlin/chapter_hashing/array_hash_map.kt create mode 100644 ja/codes/kotlin/chapter_hashing/built_in_hash.kt create mode 100644 ja/codes/kotlin/chapter_hashing/hash_map.kt create mode 100644 ja/codes/kotlin/chapter_hashing/hash_map_chaining.kt create mode 100644 ja/codes/kotlin/chapter_hashing/hash_map_open_addressing.kt create mode 100644 ja/codes/kotlin/chapter_hashing/simple_hash.kt create mode 100644 ja/codes/kotlin/chapter_heap/heap.kt create mode 100644 ja/codes/kotlin/chapter_heap/my_heap.kt create mode 100644 ja/codes/kotlin/chapter_heap/top_k.kt create mode 100644 ja/codes/kotlin/chapter_searching/binary_search.kt create mode 100644 ja/codes/kotlin/chapter_searching/binary_search_edge.kt create mode 100644 ja/codes/kotlin/chapter_searching/binary_search_insertion.kt create mode 100644 ja/codes/kotlin/chapter_searching/hashing_search.kt create mode 100644 ja/codes/kotlin/chapter_searching/linear_search.kt create mode 100644 ja/codes/kotlin/chapter_searching/two_sum.kt create mode 100644 ja/codes/kotlin/chapter_sorting/bubble_sort.kt create mode 100644 ja/codes/kotlin/chapter_sorting/bucket_sort.kt create mode 100644 ja/codes/kotlin/chapter_sorting/counting_sort.kt create mode 100644 ja/codes/kotlin/chapter_sorting/heap_sort.kt create mode 100644 ja/codes/kotlin/chapter_sorting/insertion_sort.kt create mode 100644 ja/codes/kotlin/chapter_sorting/merge_sort.kt create mode 100644 ja/codes/kotlin/chapter_sorting/quick_sort.kt create mode 100644 ja/codes/kotlin/chapter_sorting/radix_sort.kt create mode 100644 ja/codes/kotlin/chapter_sorting/selection_sort.kt create mode 100644 ja/codes/kotlin/chapter_stack_and_queue/array_deque.kt create mode 100644 ja/codes/kotlin/chapter_stack_and_queue/array_queue.kt create mode 100644 ja/codes/kotlin/chapter_stack_and_queue/array_stack.kt create mode 100644 ja/codes/kotlin/chapter_stack_and_queue/deque.kt create mode 100644 ja/codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt create mode 100644 ja/codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt create mode 100644 ja/codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt create mode 100644 ja/codes/kotlin/chapter_stack_and_queue/queue.kt create mode 100644 ja/codes/kotlin/chapter_stack_and_queue/stack.kt create mode 100644 ja/codes/kotlin/chapter_tree/array_binary_tree.kt create mode 100644 ja/codes/kotlin/chapter_tree/avl_tree.kt create mode 100644 ja/codes/kotlin/chapter_tree/binary_search_tree.kt create mode 100644 ja/codes/kotlin/chapter_tree/binary_tree.kt create mode 100644 ja/codes/kotlin/chapter_tree/binary_tree_bfs.kt create mode 100644 ja/codes/kotlin/chapter_tree/binary_tree_dfs.kt create mode 100644 ja/codes/kotlin/utils/ListNode.kt create mode 100644 ja/codes/kotlin/utils/PrintUtil.kt create mode 100644 ja/codes/kotlin/utils/TreeNode.kt create mode 100644 ja/codes/kotlin/utils/Vertex.kt create mode 100644 ja/codes/python/.gitignore create mode 100644 ja/codes/pythontutor/chapter_array_and_linkedlist/array.md create mode 100644 ja/codes/pythontutor/chapter_array_and_linkedlist/linked_list.md create mode 100644 ja/codes/pythontutor/chapter_array_and_linkedlist/my_list.md create mode 100644 ja/codes/pythontutor/chapter_backtracking/n_queens.md create mode 100644 ja/codes/pythontutor/chapter_backtracking/permutations_i.md create mode 100644 ja/codes/pythontutor/chapter_backtracking/permutations_ii.md create mode 100644 ja/codes/pythontutor/chapter_backtracking/preorder_traversal_i_compact.md create mode 100644 ja/codes/pythontutor/chapter_backtracking/preorder_traversal_ii_compact.md create mode 100644 ja/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_compact.md create mode 100644 ja/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_template.md create mode 100644 ja/codes/pythontutor/chapter_backtracking/subset_sum_i.md create mode 100644 ja/codes/pythontutor/chapter_backtracking/subset_sum_i_naive.md create mode 100644 ja/codes/pythontutor/chapter_backtracking/subset_sum_ii.md create mode 100644 ja/codes/pythontutor/chapter_computational_complexity/iteration.md create mode 100644 ja/codes/pythontutor/chapter_computational_complexity/recursion.md create mode 100644 ja/codes/pythontutor/chapter_computational_complexity/space_complexity.md create mode 100644 ja/codes/pythontutor/chapter_computational_complexity/time_complexity.md create mode 100644 ja/codes/pythontutor/chapter_computational_complexity/worst_best_time_complexity.md create mode 100644 ja/codes/pythontutor/chapter_divide_and_conquer/binary_search_recur.md create mode 100644 ja/codes/pythontutor/chapter_divide_and_conquer/build_tree.md create mode 100644 ja/codes/pythontutor/chapter_divide_and_conquer/hanota.md create mode 100644 ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_backtrack.md create mode 100644 ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_constraint_dp.md create mode 100644 ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs.md create mode 100644 ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs_mem.md create mode 100644 ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dp.md create mode 100644 ja/codes/pythontutor/chapter_dynamic_programming/coin_change.md create mode 100644 ja/codes/pythontutor/chapter_dynamic_programming/coin_change_ii.md create mode 100644 ja/codes/pythontutor/chapter_dynamic_programming/edit_distance.md create mode 100644 ja/codes/pythontutor/chapter_dynamic_programming/knapsack.md create mode 100644 ja/codes/pythontutor/chapter_dynamic_programming/min_cost_climbing_stairs_dp.md create mode 100644 ja/codes/pythontutor/chapter_dynamic_programming/min_path_sum.md create mode 100644 ja/codes/pythontutor/chapter_dynamic_programming/unbounded_knapsack.md create mode 100644 ja/codes/pythontutor/chapter_graph/graph_adjacency_list.md create mode 100644 ja/codes/pythontutor/chapter_graph/graph_adjacency_matrix.md create mode 100644 ja/codes/pythontutor/chapter_graph/graph_bfs.md create mode 100644 ja/codes/pythontutor/chapter_graph/graph_dfs.md create mode 100644 ja/codes/pythontutor/chapter_greedy/coin_change_greedy.md create mode 100644 ja/codes/pythontutor/chapter_greedy/fractional_knapsack.md create mode 100644 ja/codes/pythontutor/chapter_greedy/max_capacity.md create mode 100644 ja/codes/pythontutor/chapter_greedy/max_product_cutting.md create mode 100644 ja/codes/pythontutor/chapter_hashing/array_hash_map.md create mode 100644 ja/codes/pythontutor/chapter_hashing/hash_map_chaining.md create mode 100644 ja/codes/pythontutor/chapter_hashing/simple_hash.md create mode 100644 ja/codes/pythontutor/chapter_heap/my_heap.md create mode 100644 ja/codes/pythontutor/chapter_heap/top_k.md create mode 100644 ja/codes/pythontutor/chapter_searching/binary_search.md create mode 100644 ja/codes/pythontutor/chapter_searching/binary_search_edge.md create mode 100644 ja/codes/pythontutor/chapter_searching/binary_search_insertion.md create mode 100644 ja/codes/pythontutor/chapter_searching/two_sum.md create mode 100644 ja/codes/pythontutor/chapter_sorting/bubble_sort.md create mode 100644 ja/codes/pythontutor/chapter_sorting/bucket_sort.md create mode 100644 ja/codes/pythontutor/chapter_sorting/counting_sort.md create mode 100644 ja/codes/pythontutor/chapter_sorting/heap_sort.md create mode 100644 ja/codes/pythontutor/chapter_sorting/insertion_sort.md create mode 100644 ja/codes/pythontutor/chapter_sorting/merge_sort.md create mode 100644 ja/codes/pythontutor/chapter_sorting/quick_sort.md create mode 100644 ja/codes/pythontutor/chapter_sorting/radix_sort.md create mode 100644 ja/codes/pythontutor/chapter_sorting/selection_sort.md create mode 100644 ja/codes/pythontutor/chapter_stack_and_queue/array_queue.md create mode 100644 ja/codes/pythontutor/chapter_stack_and_queue/array_stack.md create mode 100644 ja/codes/pythontutor/chapter_stack_and_queue/linkedlist_queue.md create mode 100644 ja/codes/pythontutor/chapter_stack_and_queue/linkedlist_stack.md create mode 100644 ja/codes/pythontutor/chapter_tree/array_binary_tree.md create mode 100644 ja/codes/pythontutor/chapter_tree/binary_search_tree.md create mode 100644 ja/codes/pythontutor/chapter_tree/binary_tree_bfs.md create mode 100644 ja/codes/pythontutor/chapter_tree/binary_tree_dfs.md create mode 100644 ja/codes/ruby/chapter_array_and_linkedlist/array.rb create mode 100644 ja/codes/ruby/chapter_array_and_linkedlist/linked_list.rb create mode 100644 ja/codes/ruby/chapter_array_and_linkedlist/list.rb create mode 100644 ja/codes/ruby/chapter_array_and_linkedlist/my_list.rb create mode 100644 ja/codes/ruby/chapter_backtracking/n_queens.rb create mode 100644 ja/codes/ruby/chapter_backtracking/permutations_i.rb create mode 100644 ja/codes/ruby/chapter_backtracking/permutations_ii.rb create mode 100644 ja/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb create mode 100644 ja/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb create mode 100644 ja/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb create mode 100644 ja/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb create mode 100644 ja/codes/ruby/chapter_backtracking/subset_sum_i.rb create mode 100644 ja/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb create mode 100644 ja/codes/ruby/chapter_backtracking/subset_sum_ii.rb create mode 100644 ja/codes/ruby/chapter_computational_complexity/iteration.rb create mode 100644 ja/codes/ruby/chapter_computational_complexity/recursion.rb create mode 100644 ja/codes/ruby/chapter_computational_complexity/space_complexity.rb create mode 100644 ja/codes/ruby/chapter_computational_complexity/time_complexity.rb create mode 100644 ja/codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb create mode 100644 ja/codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb create mode 100644 ja/codes/ruby/chapter_divide_and_conquer/build_tree.rb create mode 100644 ja/codes/ruby/chapter_divide_and_conquer/hanota.rb create mode 100644 ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb create mode 100644 ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb create mode 100644 ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb create mode 100644 ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb create mode 100644 ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb create mode 100644 ja/codes/ruby/chapter_dynamic_programming/coin_change.rb create mode 100644 ja/codes/ruby/chapter_dynamic_programming/coin_change_ii.rb create mode 100644 ja/codes/ruby/chapter_dynamic_programming/edit_distance.rb create mode 100644 ja/codes/ruby/chapter_dynamic_programming/knapsack.rb create mode 100644 ja/codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb create mode 100644 ja/codes/ruby/chapter_dynamic_programming/min_path_sum.rb create mode 100644 ja/codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb create mode 100644 ja/codes/ruby/chapter_graph/graph_adjacency_list.rb create mode 100644 ja/codes/ruby/chapter_graph/graph_adjacency_matrix.rb create mode 100644 ja/codes/ruby/chapter_graph/graph_bfs.rb create mode 100644 ja/codes/ruby/chapter_graph/graph_dfs.rb create mode 100644 ja/codes/ruby/chapter_greedy/coin_change_greedy.rb create mode 100644 ja/codes/ruby/chapter_greedy/fractional_knapsack.rb create mode 100644 ja/codes/ruby/chapter_greedy/max_capacity.rb create mode 100644 ja/codes/ruby/chapter_greedy/max_product_cutting.rb create mode 100644 ja/codes/ruby/chapter_hashing/array_hash_map.rb create mode 100644 ja/codes/ruby/chapter_hashing/built_in_hash.rb create mode 100644 ja/codes/ruby/chapter_hashing/hash_map.rb create mode 100644 ja/codes/ruby/chapter_hashing/hash_map_chaining.rb create mode 100644 ja/codes/ruby/chapter_hashing/hash_map_open_addressing.rb create mode 100644 ja/codes/ruby/chapter_hashing/simple_hash.rb create mode 100644 ja/codes/ruby/chapter_heap/my_heap.rb create mode 100644 ja/codes/ruby/chapter_heap/top_k.rb create mode 100644 ja/codes/ruby/chapter_searching/binary_search.rb create mode 100644 ja/codes/ruby/chapter_searching/binary_search_edge.rb create mode 100644 ja/codes/ruby/chapter_searching/binary_search_insertion.rb create mode 100644 ja/codes/ruby/chapter_searching/hashing_search.rb create mode 100644 ja/codes/ruby/chapter_searching/linear_search.rb create mode 100644 ja/codes/ruby/chapter_searching/two_sum.rb create mode 100644 ja/codes/ruby/chapter_sorting/bubble_sort.rb create mode 100644 ja/codes/ruby/chapter_sorting/bucket_sort.rb create mode 100644 ja/codes/ruby/chapter_sorting/counting_sort.rb create mode 100644 ja/codes/ruby/chapter_sorting/heap_sort.rb create mode 100644 ja/codes/ruby/chapter_sorting/insertion_sort.rb create mode 100644 ja/codes/ruby/chapter_sorting/merge_sort.rb create mode 100644 ja/codes/ruby/chapter_sorting/quick_sort.rb create mode 100644 ja/codes/ruby/chapter_sorting/radix_sort.rb create mode 100644 ja/codes/ruby/chapter_sorting/selection_sort.rb create mode 100644 ja/codes/ruby/chapter_stack_and_queue/array_deque.rb create mode 100644 ja/codes/ruby/chapter_stack_and_queue/array_queue.rb create mode 100644 ja/codes/ruby/chapter_stack_and_queue/array_stack.rb create mode 100644 ja/codes/ruby/chapter_stack_and_queue/deque.rb create mode 100644 ja/codes/ruby/chapter_stack_and_queue/linkedlist_deque.rb create mode 100644 ja/codes/ruby/chapter_stack_and_queue/linkedlist_queue.rb create mode 100644 ja/codes/ruby/chapter_stack_and_queue/linkedlist_stack.rb create mode 100644 ja/codes/ruby/chapter_stack_and_queue/queue.rb create mode 100644 ja/codes/ruby/chapter_stack_and_queue/stack.rb create mode 100644 ja/codes/ruby/chapter_tree/array_binary_tree.rb create mode 100644 ja/codes/ruby/chapter_tree/avl_tree.rb create mode 100644 ja/codes/ruby/chapter_tree/binary_search_tree.rb create mode 100644 ja/codes/ruby/chapter_tree/binary_tree.rb create mode 100644 ja/codes/ruby/chapter_tree/binary_tree_bfs.rb create mode 100644 ja/codes/ruby/chapter_tree/binary_tree_dfs.rb create mode 100644 ja/codes/ruby/test_all.rb create mode 100644 ja/codes/ruby/utils/list_node.rb create mode 100644 ja/codes/ruby/utils/print_util.rb create mode 100644 ja/codes/ruby/utils/tree_node.rb create mode 100644 ja/codes/ruby/utils/vertex.rb create mode 100644 ja/codes/rust/.gitignore create mode 100644 ja/codes/rust/Cargo.toml create mode 100644 ja/codes/rust/chapter_array_and_linkedlist/array.rs create mode 100644 ja/codes/rust/chapter_array_and_linkedlist/linked_list.rs create mode 100644 ja/codes/rust/chapter_array_and_linkedlist/list.rs create mode 100644 ja/codes/rust/chapter_array_and_linkedlist/my_list.rs create mode 100644 ja/codes/rust/chapter_backtracking/n_queens.rs create mode 100644 ja/codes/rust/chapter_backtracking/permutations_i.rs create mode 100644 ja/codes/rust/chapter_backtracking/permutations_ii.rs create mode 100644 ja/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs create mode 100644 ja/codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs create mode 100644 ja/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs create mode 100644 ja/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs create mode 100644 ja/codes/rust/chapter_backtracking/subset_sum_i.rs create mode 100644 ja/codes/rust/chapter_backtracking/subset_sum_i_naive.rs create mode 100644 ja/codes/rust/chapter_backtracking/subset_sum_ii.rs create mode 100644 ja/codes/rust/chapter_computational_complexity/iteration.rs create mode 100644 ja/codes/rust/chapter_computational_complexity/recursion.rs create mode 100644 ja/codes/rust/chapter_computational_complexity/space_complexity.rs create mode 100644 ja/codes/rust/chapter_computational_complexity/time_complexity.rs create mode 100644 ja/codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs create mode 100644 ja/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs create mode 100644 ja/codes/rust/chapter_divide_and_conquer/build_tree.rs create mode 100644 ja/codes/rust/chapter_divide_and_conquer/hanota.rs create mode 100644 ja/codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs create mode 100644 ja/codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs create mode 100644 ja/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs create mode 100644 ja/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs create mode 100644 ja/codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs create mode 100644 ja/codes/rust/chapter_dynamic_programming/coin_change.rs create mode 100644 ja/codes/rust/chapter_dynamic_programming/coin_change_ii.rs create mode 100644 ja/codes/rust/chapter_dynamic_programming/edit_distance.rs create mode 100644 ja/codes/rust/chapter_dynamic_programming/knapsack.rs create mode 100644 ja/codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs create mode 100644 ja/codes/rust/chapter_dynamic_programming/min_path_sum.rs create mode 100644 ja/codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs create mode 100644 ja/codes/rust/chapter_graph/graph_adjacency_list.rs create mode 100644 ja/codes/rust/chapter_graph/graph_adjacency_matrix.rs create mode 100644 ja/codes/rust/chapter_graph/graph_bfs.rs create mode 100644 ja/codes/rust/chapter_graph/graph_dfs.rs create mode 100644 ja/codes/rust/chapter_greedy/coin_change_greedy.rs create mode 100644 ja/codes/rust/chapter_greedy/fractional_knapsack.rs create mode 100644 ja/codes/rust/chapter_greedy/max_capacity.rs create mode 100644 ja/codes/rust/chapter_greedy/max_product_cutting.rs create mode 100644 ja/codes/rust/chapter_hashing/array_hash_map.rs create mode 100644 ja/codes/rust/chapter_hashing/build_in_hash.rs create mode 100644 ja/codes/rust/chapter_hashing/hash_map.rs create mode 100644 ja/codes/rust/chapter_hashing/hash_map_chaining.rs create mode 100644 ja/codes/rust/chapter_hashing/hash_map_open_addressing.rs create mode 100644 ja/codes/rust/chapter_hashing/simple_hash.rs create mode 100644 ja/codes/rust/chapter_heap/heap.rs create mode 100644 ja/codes/rust/chapter_heap/my_heap.rs create mode 100644 ja/codes/rust/chapter_heap/top_k.rs create mode 100644 ja/codes/rust/chapter_searching/binary_search.rs create mode 100644 ja/codes/rust/chapter_searching/binary_search_edge.rs create mode 100644 ja/codes/rust/chapter_searching/binary_search_insertion.rs create mode 100644 ja/codes/rust/chapter_searching/hashing_search.rs create mode 100644 ja/codes/rust/chapter_searching/linear_search.rs create mode 100644 ja/codes/rust/chapter_searching/two_sum.rs create mode 100644 ja/codes/rust/chapter_sorting/bubble_sort.rs create mode 100644 ja/codes/rust/chapter_sorting/bucket_sort.rs create mode 100644 ja/codes/rust/chapter_sorting/counting_sort.rs create mode 100644 ja/codes/rust/chapter_sorting/heap_sort.rs create mode 100644 ja/codes/rust/chapter_sorting/insertion_sort.rs create mode 100644 ja/codes/rust/chapter_sorting/merge_sort.rs create mode 100644 ja/codes/rust/chapter_sorting/quick_sort.rs create mode 100644 ja/codes/rust/chapter_sorting/radix_sort.rs create mode 100644 ja/codes/rust/chapter_sorting/selection_sort.rs create mode 100644 ja/codes/rust/chapter_stack_and_queue/array_deque.rs create mode 100644 ja/codes/rust/chapter_stack_and_queue/array_queue.rs create mode 100644 ja/codes/rust/chapter_stack_and_queue/array_stack.rs create mode 100644 ja/codes/rust/chapter_stack_and_queue/deque.rs create mode 100644 ja/codes/rust/chapter_stack_and_queue/linkedlist_deque.rs create mode 100644 ja/codes/rust/chapter_stack_and_queue/linkedlist_queue.rs create mode 100644 ja/codes/rust/chapter_stack_and_queue/linkedlist_stack.rs create mode 100644 ja/codes/rust/chapter_stack_and_queue/queue.rs create mode 100644 ja/codes/rust/chapter_stack_and_queue/stack.rs create mode 100644 ja/codes/rust/chapter_tree/array_binary_tree.rs create mode 100644 ja/codes/rust/chapter_tree/avl_tree.rs create mode 100644 ja/codes/rust/chapter_tree/binary_search_tree.rs create mode 100644 ja/codes/rust/chapter_tree/binary_tree.rs create mode 100644 ja/codes/rust/chapter_tree/binary_tree_bfs.rs create mode 100644 ja/codes/rust/chapter_tree/binary_tree_dfs.rs create mode 100644 ja/codes/rust/src/include/list_node.rs create mode 100644 ja/codes/rust/src/include/mod.rs create mode 100644 ja/codes/rust/src/include/print_util.rs create mode 100644 ja/codes/rust/src/include/tree_node.rs create mode 100644 ja/codes/rust/src/include/vertex.rs create mode 100644 ja/codes/rust/src/lib.rs create mode 100644 ja/codes/swift/.gitignore create mode 100644 ja/codes/swift/Package.resolved create mode 100644 ja/codes/swift/Package.swift create mode 100644 ja/codes/swift/chapter_array_and_linkedlist/array.swift create mode 100644 ja/codes/swift/chapter_array_and_linkedlist/linked_list.swift create mode 100644 ja/codes/swift/chapter_array_and_linkedlist/list.swift create mode 100644 ja/codes/swift/chapter_array_and_linkedlist/my_list.swift create mode 100644 ja/codes/swift/chapter_backtracking/n_queens.swift create mode 100644 ja/codes/swift/chapter_backtracking/permutations_i.swift create mode 100644 ja/codes/swift/chapter_backtracking/permutations_ii.swift create mode 100644 ja/codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift create mode 100644 ja/codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift create mode 100644 ja/codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift create mode 100644 ja/codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift create mode 100644 ja/codes/swift/chapter_backtracking/subset_sum_i.swift create mode 100644 ja/codes/swift/chapter_backtracking/subset_sum_i_naive.swift create mode 100644 ja/codes/swift/chapter_backtracking/subset_sum_ii.swift create mode 100644 ja/codes/swift/chapter_computational_complexity/iteration.swift create mode 100644 ja/codes/swift/chapter_computational_complexity/recursion.swift create mode 100644 ja/codes/swift/chapter_computational_complexity/space_complexity.swift create mode 100644 ja/codes/swift/chapter_computational_complexity/time_complexity.swift create mode 100644 ja/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift create mode 100644 ja/codes/swift/chapter_divide_and_conquer/binary_search_recur.swift create mode 100644 ja/codes/swift/chapter_divide_and_conquer/build_tree.swift create mode 100644 ja/codes/swift/chapter_divide_and_conquer/hanota.swift create mode 100644 ja/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift create mode 100644 ja/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift create mode 100644 ja/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift create mode 100644 ja/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift create mode 100644 ja/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift create mode 100644 ja/codes/swift/chapter_dynamic_programming/coin_change.swift create mode 100644 ja/codes/swift/chapter_dynamic_programming/coin_change_ii.swift create mode 100644 ja/codes/swift/chapter_dynamic_programming/edit_distance.swift create mode 100644 ja/codes/swift/chapter_dynamic_programming/knapsack.swift create mode 100644 ja/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift create mode 100644 ja/codes/swift/chapter_dynamic_programming/min_path_sum.swift create mode 100644 ja/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift create mode 100644 ja/codes/swift/chapter_graph/graph_adjacency_list.swift create mode 100644 ja/codes/swift/chapter_graph/graph_adjacency_list_target.swift create mode 100644 ja/codes/swift/chapter_graph/graph_adjacency_matrix.swift create mode 100644 ja/codes/swift/chapter_graph/graph_bfs.swift create mode 100644 ja/codes/swift/chapter_graph/graph_dfs.swift create mode 100644 ja/codes/swift/chapter_greedy/coin_change_greedy.swift create mode 100644 ja/codes/swift/chapter_greedy/fractional_knapsack.swift create mode 100644 ja/codes/swift/chapter_greedy/max_capacity.swift create mode 100644 ja/codes/swift/chapter_greedy/max_product_cutting.swift create mode 100644 ja/codes/swift/chapter_hashing/array_hash_map.swift create mode 100644 ja/codes/swift/chapter_hashing/built_in_hash.swift create mode 100644 ja/codes/swift/chapter_hashing/hash_map.swift create mode 100644 ja/codes/swift/chapter_hashing/hash_map_chaining.swift create mode 100644 ja/codes/swift/chapter_hashing/hash_map_open_addressing.swift create mode 100644 ja/codes/swift/chapter_hashing/simple_hash.swift create mode 100644 ja/codes/swift/chapter_heap/heap.swift create mode 100644 ja/codes/swift/chapter_heap/my_heap.swift create mode 100644 ja/codes/swift/chapter_heap/top_k.swift create mode 100644 ja/codes/swift/chapter_searching/binary_search.swift create mode 100644 ja/codes/swift/chapter_searching/binary_search_edge.swift create mode 100644 ja/codes/swift/chapter_searching/binary_search_insertion.swift create mode 100644 ja/codes/swift/chapter_searching/binary_search_insertion_target.swift create mode 100644 ja/codes/swift/chapter_searching/hashing_search.swift create mode 100644 ja/codes/swift/chapter_searching/linear_search.swift create mode 100644 ja/codes/swift/chapter_searching/two_sum.swift create mode 100644 ja/codes/swift/chapter_sorting/bubble_sort.swift create mode 100644 ja/codes/swift/chapter_sorting/bucket_sort.swift create mode 100644 ja/codes/swift/chapter_sorting/counting_sort.swift create mode 100644 ja/codes/swift/chapter_sorting/heap_sort.swift create mode 100644 ja/codes/swift/chapter_sorting/insertion_sort.swift create mode 100644 ja/codes/swift/chapter_sorting/merge_sort.swift create mode 100644 ja/codes/swift/chapter_sorting/quick_sort.swift create mode 100644 ja/codes/swift/chapter_sorting/radix_sort.swift create mode 100644 ja/codes/swift/chapter_sorting/selection_sort.swift create mode 100644 ja/codes/swift/chapter_stack_and_queue/array_deque.swift create mode 100644 ja/codes/swift/chapter_stack_and_queue/array_queue.swift create mode 100644 ja/codes/swift/chapter_stack_and_queue/array_stack.swift create mode 100644 ja/codes/swift/chapter_stack_and_queue/deque.swift create mode 100644 ja/codes/swift/chapter_stack_and_queue/linkedlist_deque.swift create mode 100644 ja/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift create mode 100644 ja/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift create mode 100644 ja/codes/swift/chapter_stack_and_queue/queue.swift create mode 100644 ja/codes/swift/chapter_stack_and_queue/stack.swift create mode 100644 ja/codes/swift/chapter_tree/array_binary_tree.swift create mode 100644 ja/codes/swift/chapter_tree/avl_tree.swift create mode 100644 ja/codes/swift/chapter_tree/binary_search_tree.swift create mode 100644 ja/codes/swift/chapter_tree/binary_tree.swift create mode 100644 ja/codes/swift/chapter_tree/binary_tree_bfs.swift create mode 100644 ja/codes/swift/chapter_tree/binary_tree_dfs.swift create mode 100644 ja/codes/swift/utils/ListNode.swift create mode 100644 ja/codes/swift/utils/Pair.swift create mode 100644 ja/codes/swift/utils/PrintUtil.swift create mode 100644 ja/codes/swift/utils/TreeNode.swift create mode 100644 ja/codes/swift/utils/Vertex.swift create mode 100644 ja/codes/typescript/.gitignore create mode 100644 ja/codes/typescript/.prettierrc create mode 100644 ja/codes/typescript/chapter_array_and_linkedlist/array.ts create mode 100644 ja/codes/typescript/chapter_array_and_linkedlist/linked_list.ts create mode 100644 ja/codes/typescript/chapter_array_and_linkedlist/list.ts create mode 100644 ja/codes/typescript/chapter_array_and_linkedlist/my_list.ts create mode 100644 ja/codes/typescript/chapter_backtracking/n_queens.ts create mode 100644 ja/codes/typescript/chapter_backtracking/permutations_i.ts create mode 100644 ja/codes/typescript/chapter_backtracking/permutations_ii.ts create mode 100644 ja/codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts create mode 100644 ja/codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts create mode 100644 ja/codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts create mode 100644 ja/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts create mode 100644 ja/codes/typescript/chapter_backtracking/subset_sum_i.ts create mode 100644 ja/codes/typescript/chapter_backtracking/subset_sum_i_naive.ts create mode 100644 ja/codes/typescript/chapter_backtracking/subset_sum_ii.ts create mode 100644 ja/codes/typescript/chapter_computational_complexity/iteration.ts create mode 100644 ja/codes/typescript/chapter_computational_complexity/recursion.ts create mode 100644 ja/codes/typescript/chapter_computational_complexity/space_complexity.ts create mode 100644 ja/codes/typescript/chapter_computational_complexity/time_complexity.ts create mode 100644 ja/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts create mode 100644 ja/codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts create mode 100644 ja/codes/typescript/chapter_divide_and_conquer/build_tree.ts create mode 100644 ja/codes/typescript/chapter_divide_and_conquer/hanota.ts create mode 100644 ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts create mode 100644 ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts create mode 100644 ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts create mode 100644 ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts create mode 100644 ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts create mode 100644 ja/codes/typescript/chapter_dynamic_programming/coin_change.ts create mode 100644 ja/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts create mode 100644 ja/codes/typescript/chapter_dynamic_programming/edit_distance.ts create mode 100644 ja/codes/typescript/chapter_dynamic_programming/knapsack.ts create mode 100644 ja/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts create mode 100644 ja/codes/typescript/chapter_dynamic_programming/min_path_sum.ts create mode 100644 ja/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts create mode 100644 ja/codes/typescript/chapter_graph/graph_adjacency_list.ts create mode 100644 ja/codes/typescript/chapter_graph/graph_adjacency_matrix.ts create mode 100644 ja/codes/typescript/chapter_graph/graph_bfs.ts create mode 100644 ja/codes/typescript/chapter_graph/graph_dfs.ts create mode 100644 ja/codes/typescript/chapter_greedy/coin_change_greedy.ts create mode 100644 ja/codes/typescript/chapter_greedy/fractional_knapsack.ts create mode 100644 ja/codes/typescript/chapter_greedy/max_capacity.ts create mode 100644 ja/codes/typescript/chapter_greedy/max_product_cutting.ts create mode 100644 ja/codes/typescript/chapter_hashing/array_hash_map.ts create mode 100644 ja/codes/typescript/chapter_hashing/hash_map.ts create mode 100644 ja/codes/typescript/chapter_hashing/hash_map_chaining.ts create mode 100644 ja/codes/typescript/chapter_hashing/hash_map_open_addressing.ts create mode 100644 ja/codes/typescript/chapter_hashing/simple_hash.ts create mode 100644 ja/codes/typescript/chapter_heap/my_heap.ts create mode 100644 ja/codes/typescript/chapter_heap/top_k.ts create mode 100644 ja/codes/typescript/chapter_searching/binary_search.ts create mode 100644 ja/codes/typescript/chapter_searching/binary_search_edge.ts create mode 100644 ja/codes/typescript/chapter_searching/binary_search_insertion.ts create mode 100644 ja/codes/typescript/chapter_searching/hashing_search.ts create mode 100644 ja/codes/typescript/chapter_searching/linear_search.ts create mode 100644 ja/codes/typescript/chapter_searching/two_sum.ts create mode 100644 ja/codes/typescript/chapter_sorting/bubble_sort.ts create mode 100644 ja/codes/typescript/chapter_sorting/bucket_sort.ts create mode 100644 ja/codes/typescript/chapter_sorting/counting_sort.ts create mode 100644 ja/codes/typescript/chapter_sorting/heap_sort.ts create mode 100644 ja/codes/typescript/chapter_sorting/insertion_sort.ts create mode 100644 ja/codes/typescript/chapter_sorting/merge_sort.ts create mode 100644 ja/codes/typescript/chapter_sorting/quick_sort.ts create mode 100644 ja/codes/typescript/chapter_sorting/radix_sort.ts create mode 100644 ja/codes/typescript/chapter_sorting/selection_sort.ts create mode 100644 ja/codes/typescript/chapter_stack_and_queue/array_deque.ts create mode 100644 ja/codes/typescript/chapter_stack_and_queue/array_queue.ts create mode 100644 ja/codes/typescript/chapter_stack_and_queue/array_stack.ts create mode 100644 ja/codes/typescript/chapter_stack_and_queue/deque.ts create mode 100644 ja/codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts create mode 100644 ja/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts create mode 100644 ja/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts create mode 100644 ja/codes/typescript/chapter_stack_and_queue/queue.ts create mode 100644 ja/codes/typescript/chapter_stack_and_queue/stack.ts create mode 100644 ja/codes/typescript/chapter_tree/array_binary_tree.ts create mode 100644 ja/codes/typescript/chapter_tree/avl_tree.ts create mode 100644 ja/codes/typescript/chapter_tree/binary_search_tree.ts create mode 100644 ja/codes/typescript/chapter_tree/binary_tree.ts create mode 100644 ja/codes/typescript/chapter_tree/binary_tree_bfs.ts create mode 100644 ja/codes/typescript/chapter_tree/binary_tree_dfs.ts create mode 100644 ja/codes/typescript/modules/ListNode.ts create mode 100644 ja/codes/typescript/modules/PrintUtil.ts create mode 100644 ja/codes/typescript/modules/TreeNode.ts create mode 100644 ja/codes/typescript/modules/Vertex.ts create mode 100644 ja/codes/typescript/package.json create mode 100644 ja/codes/typescript/tsconfig.json create mode 100644 ja/codes/zig/.gitignore create mode 100644 ja/codes/zig/.vscode/launch.json create mode 100644 ja/codes/zig/.vscode/settings.json create mode 100644 ja/codes/zig/.vscode/tasks.json create mode 100644 ja/codes/zig/build.zig create mode 100644 ja/codes/zig/chapter_array_and_linkedlist/array.zig create mode 100644 ja/codes/zig/chapter_array_and_linkedlist/linked_list.zig create mode 100644 ja/codes/zig/chapter_array_and_linkedlist/list.zig create mode 100644 ja/codes/zig/chapter_array_and_linkedlist/my_list.zig create mode 100644 ja/codes/zig/chapter_computational_complexity/iteration.zig create mode 100644 ja/codes/zig/chapter_computational_complexity/recursion.zig create mode 100644 ja/codes/zig/chapter_computational_complexity/space_complexity.zig create mode 100644 ja/codes/zig/chapter_computational_complexity/time_complexity.zig create mode 100644 ja/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig create mode 100644 ja/codes/zig/chapter_dynamic_programming/climbing_stairs_backtrack.zig create mode 100644 ja/codes/zig/chapter_dynamic_programming/climbing_stairs_constraint_dp.zig create mode 100644 ja/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs.zig create mode 100644 ja/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs_mem.zig create mode 100644 ja/codes/zig/chapter_dynamic_programming/climbing_stairs_dp.zig create mode 100644 ja/codes/zig/chapter_dynamic_programming/coin_change.zig create mode 100644 ja/codes/zig/chapter_dynamic_programming/coin_change_ii.zig create mode 100644 ja/codes/zig/chapter_dynamic_programming/edit_distance.zig create mode 100644 ja/codes/zig/chapter_dynamic_programming/knapsack.zig create mode 100644 ja/codes/zig/chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig create mode 100644 ja/codes/zig/chapter_dynamic_programming/min_path_sum.zig create mode 100644 ja/codes/zig/chapter_dynamic_programming/unbounded_knapsack.zig create mode 100644 ja/codes/zig/chapter_hashing/array_hash_map.zig create mode 100644 ja/codes/zig/chapter_hashing/hash_map.zig create mode 100644 ja/codes/zig/chapter_heap/heap.zig create mode 100644 ja/codes/zig/chapter_heap/my_heap.zig create mode 100644 ja/codes/zig/chapter_searching/binary_search.zig create mode 100644 ja/codes/zig/chapter_searching/hashing_search.zig create mode 100644 ja/codes/zig/chapter_searching/linear_search.zig create mode 100644 ja/codes/zig/chapter_searching/two_sum.zig create mode 100644 ja/codes/zig/chapter_sorting/bubble_sort.zig create mode 100644 ja/codes/zig/chapter_sorting/insertion_sort.zig create mode 100644 ja/codes/zig/chapter_sorting/merge_sort.zig create mode 100644 ja/codes/zig/chapter_sorting/quick_sort.zig create mode 100644 ja/codes/zig/chapter_sorting/radix_sort.zig create mode 100644 ja/codes/zig/chapter_stack_and_queue/array_queue.zig create mode 100644 ja/codes/zig/chapter_stack_and_queue/array_stack.zig create mode 100644 ja/codes/zig/chapter_stack_and_queue/deque.zig create mode 100644 ja/codes/zig/chapter_stack_and_queue/linkedlist_deque.zig create mode 100644 ja/codes/zig/chapter_stack_and_queue/linkedlist_queue.zig create mode 100644 ja/codes/zig/chapter_stack_and_queue/linkedlist_stack.zig create mode 100644 ja/codes/zig/chapter_stack_and_queue/queue.zig create mode 100644 ja/codes/zig/chapter_stack_and_queue/stack.zig create mode 100644 ja/codes/zig/chapter_tree/avl_tree.zig create mode 100644 ja/codes/zig/chapter_tree/binary_search_tree.zig create mode 100644 ja/codes/zig/chapter_tree/binary_tree.zig create mode 100644 ja/codes/zig/chapter_tree/binary_tree_bfs.zig create mode 100644 ja/codes/zig/chapter_tree/binary_tree_dfs.zig create mode 100644 ja/codes/zig/include/PrintUtil.zig create mode 100644 ja/codes/zig/include/include.zig create mode 100644 ja/codes/zig/main.zig create mode 100644 ja/codes/zig/utils/ListNode.zig create mode 100644 ja/codes/zig/utils/TreeNode.zig create mode 100644 ja/codes/zig/utils/format.zig create mode 100644 ja/codes/zig/utils/utils.zig create mode 100644 ja/docs/chapter_paperbook/index.md diff --git a/ja/CONTRIBUTING.md b/ja/CONTRIBUTING.md deleted file mode 100644 index ecbac04f4..000000000 --- a/ja/CONTRIBUTING.md +++ /dev/null @@ -1,134 +0,0 @@ -# 中国語から日本語への貢献ガイドライン - -「Hello アルゴリズム」を中国語から日本語に翻訳するにあたり、以下のアプローチを採用しています: - -1. **AI翻訳**: 大規模言語モデルを使用して初期翻訳を実施します。 -2. **人による最適化**: 機械生成された出力を手動で改良し、正確性と自然さを確保します。 -3. **プルリクエストレビュー**: 最適化された翻訳は、GitHubのプルリクエストワークフローを通じてレビュアーによって二重チェックされます。 -4. さらなる改善のため、ステップ `2.` と `3.` を繰り返します。 - -translation_pipeline - -## 参加方法 - -以下の基準を満たす貢献者を求めています: - -- **技術的背景**: コンピュータサイエンス、特にデータ構造とアルゴリズムに関する強固な基礎知識 -- **言語スキル**: 日本語ネイティブまたは日本語に精通した方、中国語の読解力 -- **利用可能な時間**: オープンソースプロジェクトへの貢献に専念し、長期的な翻訳作業に参加する意欲 - -つまり、私たちの貢献者は、さまざまな言語背景を持つコンピュータサイエンティスト、エンジニア、学生であり、それぞれの目的には異なる焦点があります: - -- **中国語読解力を持つ日本語ネイティブ**: 中国語版と日本語版の間の翻訳の正確性と一貫性を確保する -- **日本語に精通した中国語話者**: 日本語コンテンツの自然さと流暢さを向上させ、自然で読みやすいものにする - -> [!note] -> 参加にご興味がある方は、お気軽に krahetx@gmail.com またはWeChat `krahets-jyd` までご連絡ください。 -> -> 進捗管理とタスク割り当てには、この[Notionページ](https://hello-algo.notion.site/chinese-to-english)を使用しています。詳細はこちらをご覧ください。 - -## 翻訳プロセス - -> [!important] -> 作業を開始する前に、GitHubのプルリクエストワークフローに慣れ、以下の「翻訳基準」と「翻訳のための疑似コード」を必ずお読みください。 - -1. **タスク割り当て**: Notionワークスペースでタスクを自己割り当てします。 -2. **翻訳**: ローカルPCで翻訳を最適化します。詳細は以下の「翻訳疑似コード」セクションを参照してください。 -3. **ピアレビュー**: プルリクエスト(PR)を提出する前に、変更を慎重にレビューしてください。PRは2名のレビュアーの承認後にメインブランチにマージされます。 - -## 翻訳基準 - -> [!tip] -> **「正確性」と「自然さ」は、主に中国語を理解できる日本語ネイティブスピーカーによって扱われます。** -> -> 場合によっては、「正確性(一貫性)」と「自然さ」はトレードオフの関係にあり、一方を最適化すると他方に大きな影響を与える可能性があります。そのような場合は、プルリクエストにコメントを残して議論してください。 - -**正確性**: - -- [用語集](https://www.hello-algo.com/chapter_appendix/terminology/)セクションを参照して、翻訳全体で用語の一貫性を保ちます。 -- 技術的な正確性を優先し、中国語版のトーンとスタイルを維持します。 -- 修正が正確で包括的であることを確保するため、常に中国語版の内容とコンテキストを考慮してください。 - -**自然さ**: - -- 翻訳は日本語の表現慣習に従い、自然で流暢に読めるようにすべきです。 -- 記事を調和させるために、常にコンテンツのコンテキストを考慮してください。 -- 中国語と日本語の文化的な違いに注意してください。例えば、中国語の「拼音」は日本語には存在しません。 -- 最適化された文が元の意味を変える可能性がある場合は、議論のためにコメントを追加してください。 - -**フォーマット**: - -- 図表は展開時に自動的に番号付けされるため、手動で番号を付けないでください。 -- バグ修正を除き、各PRは管理可能なレビューサイズを確保するため、少なくとも1つの完全なドキュメントをカバーすべきです。 - -**レビュー**: - -- レビュー中は、変更の評価を優先し、必要に応じて周囲のコンテキストを参照してください。 -- お互いの視点から学ぶことで、より良い翻訳とより一貫性のある結果につながります。 - -## 翻訳疑似コード - -以下の疑似コードは、典型的な翻訳プロセスのステップをモデル化しています。 - -```python -def optimize_translation(markdown_texts, lang_skill): - """翻訳を最適化する""" - for sentence in markdown_texts: - """正確性は主に中国語を理解できる日本語ネイティブスピーカーによって処理される""" - if lang_skill is "日本語ネイティブ + 中国語読解力": - if is_accurate_Chinese_to_Japanese(sentence): - continue - # 正確性を最適化 - result = refine_accuracy(sentence) - - """ - 自然さは主に日本語ネイティブスピーカーによって処理され、 - 副次的に中国語話者によって処理される - """ - if is_authentic_Japanese(sentence): - continue - # 自然さを最適化 - result = refine_authenticity(sentence) - # 一貫性を損なう可能性がある場合はPRにコメントを追加 - if break_consistency(result): - add_comment(description) - - pull_request = submit_pull_request(markdown_texts) - # PRは2名以上のレビュアーによる承認後にマージされる - while count_approvals(pull_request) < 2: - continue - merge(pull_request) -``` - -以下はレビュアー向けの疑似コードです: - -```python -def review_pull_requests(pull_request, lang_skill): - """PRをレビューする""" - # PR内のすべての変更をループ - while is_anything_left_to_review(pull_request): - change = get_next_change(pull_request) - - """正確性は主に中国語を理解できる日本語ネイティブスピーカーによって処理される""" - if lang_skill is "日本語ネイティブ + 中国語読解力": - # 中国語版と日本語版の間の正確性(一貫性)をチェック - if is_accurate_Chinese_to_Japanese(change): - continue - # 正確性(一貫性)を最適化 - result = refine_accuracy(change) - # PRにコメントを追加 - add_comment(result) - - """ - 自然さは主に日本語ネイティブスピーカーによって処理され、 - 副次的に中国語話者によって処理される - """ - if is_authentic_Japanese(change): - continue - # 自然な日本語でない場合は自然さを最適化 - result = refine_authenticity(change) - # PRにコメントを追加 - add_comment(result) - - approve(pull_request) -``` \ No newline at end of file diff --git a/ja/README.md b/ja/README.md index 733cc7763..a71a08009 100644 --- a/ja/README.md +++ b/ja/README.md @@ -6,12 +6,14 @@

hello-algo-typing-svg
- アニメーションで図解、ワンクリック実行のデータ構造とアルゴリズム入門講座 + アニメーション図解とワンクリック実行コードで学べる、データ構造とアルゴリズムの入門書

+ +

@@ -49,47 +51,45 @@ ## この本について -このオープンソースプロジェクトは、データ構造とアルゴリズムの無料で初心者向けの入門講座を作成することを目的としています。 +本プロジェクトは、無料かつオープンソースで、初心者にもやさしいデータ構造とアルゴリズムの入門書を作ることを目的としています。 -- アニメーションによる図解、わかりやすい内容、なめらかな学習曲線により、初心者がデータ構造とアルゴリズムの「知識マップ」を探索できます。 -- ワンクリックでコードを実行でき、読者のプログラミングスキルを向上させ、アルゴリズムの動作原理とデータ構造の基礎となる実装を理解できます。 -- 教えることで学ぶことを促進し、質問や洞察を自由に共有してください。議論を通じて一緒に成長しましょう。 +- 全編をアニメーション図解で構成し、わかりやすい内容と無理のない学習曲線によって、初学者がデータ構造とアルゴリズムの知識地図をたどれるようにしています。 +- ソースコードはワンクリックで実行でき、演習を通してプログラミング力を高めながら、アルゴリズムの動作原理とデータ構造の内部実装を理解できます。 +- 学び合いを大切にしており、コメント欄での質問や知見の共有を歓迎します。議論を通じて一緒に成長していきましょう。 -この本が役立つと思われた場合は、スター :star: を付けてサポートしてください。ありがとうございます! +本書が役に立ったら、ページ右上の Star :star: で応援していただけると嬉しいです。ありがとうございます。 ## 推薦の言葉 -> データ構造とアルゴリズムに関するわかりやすい本で、読者が頭と手を使って学ぶように導きます。アルゴリズム初心者に強くお勧めします! +> 「平易でわかりやすいデータ構造・アルゴリズム入門書であり、読者を頭と手の両方を使う学びへと導いてくれます。アルゴリズム初学者に強く薦めます。」 > -> **—— 邓俊辉,清華大学コンピュータサイエンス技術学部** +> **—— 邓俊辉,清華大学計算機科学技術学部教授** -> データ構造とアルゴリズムを学んでいたときに『Hello Algo』があったなら、10 倍簡単だったでしょう! +> 「もし当時『Hello Algo』があれば、データ構造とアルゴリズムの学習は 10 倍は楽だったはずです!」 > -> **—— 李沐,Amazon シニアプリンシパルサイエンティスト** +> **—— 李沐,Amazon シニア・プリンシパル・サイエンティスト** -## 特別な感謝 +## 謝辞

Warp-Github-LG-02

-[Warp は複数の AI エージェントと共にコーディングするために構築されています。](https://go.warp.dev/hello-algo) +[Warp は複数の AI エージェントとともにコーディングするために作られています。](https://go.warp.dev/hello-algo) + +Warp ターミナルは、洗練された UI と使いやすい AI を兼ね備えており、非常に優れた体験を提供してくれます。 ## 貢献 -> [!Important] -> -> 日本語版の翻訳作業へのご参加を歓迎します!詳細は [CONTRIBUTING.md](CONTRIBUTING.md) をご覧ください。 +本書は現在も継続的に更新されており、読者により良い学習コンテンツを届けるため、プロジェクトへの参加を歓迎しています。 -このオープンソースブックは継続的に更新されており、読者により良い学習コンテンツを提供するため、このプロジェクトへの参加を歓迎します。 +- [内容の修正](https://www.hello-algo.com/ja/chapter_appendix/contribution/):文法ミス、内容の欠落、表現の曖昧さ、無効なリンク、コードのバグなどがあれば、修正またはコメント欄でのご指摘をお願いします。 +- [コードの移植](https://github.com/krahets/hello-algo/issues/15):Python、Java、C++、Go、JavaScript など、現在対応している 12 言語のコード整備への貢献をお待ちしています。 -- [内容の修正](https://www.hello-algo.com/ja/chapter_appendix/contribution/):文法エラー、内容の欠落、曖昧さ、無効なリンク、コードのバグなど、コメントセクションで間違いを修正したり指摘したりしてください。 -- [コードの移植](https://github.com/krahets/hello-algo/issues/15):さまざまなプログラミング言語でのご貢献をお待ちしています。現在、Python、Java、C++、Go、JavaScript を含む 12 言語をサポートしています。 +ご意見・ご提案を歓迎します。ご不明点があれば Issue を作成するか、WeChat の `krahets-jyd` までご連絡ください。 -貴重なご提案とフィードバックを歓迎します。ご質問がある場合は、Issues を提出するか、WeChat:`krahets-jyd` でお問い合わせください。 - -この本のすべての貢献者に感謝を捧げたいと思います。彼らの無私の献身により、この本がより良いものになりました。貢献者の皆様: +本書をより良いものにしてくれた、すべての執筆・貢献者の皆さんに感謝します。無私の協力によって、このオープンソース書籍は支えられています。

@@ -99,4 +99,4 @@ ## ライセンス -このリポジトリ内のテキスト、コード、画像、写真、動画は [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) の下でライセンスされています。 +このリポジトリに含まれるテキスト、コード、画像、写真、動画は、[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) の下でライセンスされています。 diff --git a/ja/codes/c/.gitignore b/ja/codes/c/.gitignore new file mode 100644 index 000000000..698ee4e21 --- /dev/null +++ b/ja/codes/c/.gitignore @@ -0,0 +1,9 @@ +# Ignore all +* +# Unignore all with extensions +!*.* +# Unignore all dirs +!*/ +*.dSYM/ + +build/ diff --git a/ja/codes/c/CMakeLists.txt b/ja/codes/c/CMakeLists.txt new file mode 100644 index 000000000..bb5f8f6a9 --- /dev/null +++ b/ja/codes/c/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.10) +project(hello_algo C) + +set(CMAKE_C_STANDARD 11) + +include_directories(./include) + +add_subdirectory(chapter_computational_complexity) +add_subdirectory(chapter_array_and_linkedlist) +add_subdirectory(chapter_stack_and_queue) +add_subdirectory(chapter_hashing) +add_subdirectory(chapter_tree) +add_subdirectory(chapter_heap) +add_subdirectory(chapter_graph) +add_subdirectory(chapter_searching) +add_subdirectory(chapter_sorting) +add_subdirectory(chapter_divide_and_conquer) +add_subdirectory(chapter_backtracking) +add_subdirectory(chapter_dynamic_programming) +add_subdirectory(chapter_greedy) diff --git a/ja/codes/c/chapter_array_and_linkedlist/CMakeLists.txt b/ja/codes/c/chapter_array_and_linkedlist/CMakeLists.txt new file mode 100644 index 000000000..29677a0be --- /dev/null +++ b/ja/codes/c/chapter_array_and_linkedlist/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(array array.c) +add_executable(linked_list linked_list.c) +add_executable(my_list my_list.c) \ No newline at end of file diff --git a/ja/codes/c/chapter_array_and_linkedlist/array.c b/ja/codes/c/chapter_array_and_linkedlist/array.c new file mode 100644 index 000000000..844a08386 --- /dev/null +++ b/ja/codes/c/chapter_array_and_linkedlist/array.c @@ -0,0 +1,114 @@ +/** + * File: array.c + * Created Time: 2022-12-20 + * Author: MolDuM (moldum@163.com) + */ + +#include "../utils/common.h" + +/* 要素へランダムアクセス */ +int randomAccess(int *nums, int size) { + // 区間 [0, size) からランダムに 1 つの数を選ぶ + int randomIndex = rand() % size; + // ランダムな要素を取得して返す + int randomNum = nums[randomIndex]; + return randomNum; +} + +/* 配列長を拡張する */ +int *extend(int *nums, int size, int enlarge) { + // 拡張後の長さを持つ配列を初期化する + int *res = (int *)malloc(sizeof(int) * (size + enlarge)); + // 元の配列の全要素を新しい配列にコピー + for (int i = 0; i < size; i++) { + res[i] = nums[i]; + } + // 拡張後の領域を初期化する + for (int i = size; i < size + enlarge; i++) { + res[i] = 0; + } + // 拡張後の新しい配列を返す + return res; +} + +/* 配列の index 番目に要素 num を挿入 */ +void insert(int *nums, int size, int num, int index) { + // インデックス index 以降の全要素を 1 つ後ろへ移動する + for (int i = size - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // index の要素に num を代入する + nums[index] = num; +} + +/* index の要素を削除する */ +// 注意: stdio.h が remove 識別子を使用している +void removeItem(int *nums, int size, int index) { + // インデックス index より後ろの全要素を 1 つ前へ移動する + for (int i = index; i < size - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* 配列を走査 */ +void traverse(int *nums, int size) { + int count = 0; + // インデックスで配列を走査 + for (int i = 0; i < size; i++) { + count += nums[i]; + } +} + +/* 配列内で指定要素を探す */ +int find(int *nums, int size, int target) { + for (int i = 0; i < size; i++) { + if (nums[i] == target) + return i; + } + return -1; +} + +/* Driver Code */ +int main() { + /* 配列を初期化 */ + int size = 5; + int arr[5]; + printf("配列 arr = "); + printArray(arr, size); + + int nums[] = {1, 3, 2, 5, 4}; + printf("配列 nums = "); + printArray(nums, size); + + /* ランダムアクセス */ + int randomNum = randomAccess(nums, size); + printf("nums からランダムな要素 %d を取得", randomNum); + + /* 長さを拡張 */ + int enlarge = 3; + int *res = extend(nums, size, enlarge); + size += enlarge; + printf("配列の長さを 8 に拡張し、nums = "); + printArray(res, size); + + /* 要素を挿入する */ + insert(res, size, 6, 3); + printf("インデックス 3 に数字 6 を挿入し、nums = "); + printArray(res, size); + + /* 要素を削除 */ + removeItem(res, size, 2); + printf("インデックス 2 の要素を削除し、nums = "); + printArray(res, size); + + /* 配列を走査 */ + traverse(res, size); + + /* 要素を探索する */ + int index = find(res, size, 3); + printf("res 内で要素 3 を検索し、インデックス = %d\n", index); + + /* メモリを解放する */ + free(res); + return 0; +} diff --git a/ja/codes/c/chapter_array_and_linkedlist/linked_list.c b/ja/codes/c/chapter_array_and_linkedlist/linked_list.c new file mode 100644 index 000000000..8dff4773c --- /dev/null +++ b/ja/codes/c/chapter_array_and_linkedlist/linked_list.c @@ -0,0 +1,89 @@ +/** + * File: linked_list.c + * Created Time: 2023-01-12 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 連結リストでノード n0 の後ろにノード P を挿入する */ +void insert(ListNode *n0, ListNode *P) { + ListNode *n1 = n0->next; + P->next = n1; + n0->next = P; +} + +/* 連結リストでノード n0 の直後のノードを削除する */ +// 注意: stdio.h が remove 識別子を使用している +void removeItem(ListNode *n0) { + if (!n0->next) + return; + // n0 -> P -> n1 + ListNode *P = n0->next; + ListNode *n1 = P->next; + n0->next = n1; + // メモリを解放する + free(P); +} + +/* 連結リスト内で index 番目のノードにアクセス */ +ListNode *access(ListNode *head, int index) { + for (int i = 0; i < index; i++) { + if (head == NULL) + return NULL; + head = head->next; + } + return head; +} + +/* 連結リストで値が target の最初のノードを探す */ +int find(ListNode *head, int target) { + int index = 0; + while (head) { + if (head->val == target) + return index; + head = head->next; + index++; + } + return -1; +} + +/* Driver Code */ +int main() { + /* 連結リストを初期化 */ + // 各ノードを初期化 + ListNode *n0 = newListNode(1); + ListNode *n1 = newListNode(3); + ListNode *n2 = newListNode(2); + ListNode *n3 = newListNode(5); + ListNode *n4 = newListNode(4); + // ノード間の参照を構築する + n0->next = n1; + n1->next = n2; + n2->next = n3; + n3->next = n4; + printf("初期化後の連結リストは\r\n"); + printLinkedList(n0); + + /* ノードを挿入 */ + insert(n0, newListNode(0)); + printf("ノード挿入後の連結リストは\r\n"); + printLinkedList(n0); + + /* ノードを削除 */ + removeItem(n0); + printf("ノード削除後の連結リストは\r\n"); + printLinkedList(n0); + + /* ノードにアクセス */ + ListNode *node = access(n0, 3); + printf("連結リストのインデックス 3 にあるノードの値 = %d\r\n", node->val); + + /* ノードを探索 */ + int index = find(n0, 2); + printf("連結リスト内で値が 2 のノードのインデックス = %d\r\n", index); + + // メモリを解放する + freeMemoryLinkedList(n0); + return 0; +} diff --git a/ja/codes/c/chapter_array_and_linkedlist/my_list.c b/ja/codes/c/chapter_array_and_linkedlist/my_list.c new file mode 100644 index 000000000..e847cf6af --- /dev/null +++ b/ja/codes/c/chapter_array_and_linkedlist/my_list.c @@ -0,0 +1,163 @@ +/** + * File: my_list.c + * Created Time: 2023-01-12 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* リストクラス */ +typedef struct { + int *arr; // 配列(リスト要素を格納) + int capacity; // リスト容量 + int size; // リストのサイズ + int extendRatio; // リストが拡張されるたびの倍率 +} MyList; + +void extendCapacity(MyList *nums); + +/* コンストラクタ */ +MyList *newMyList() { + MyList *nums = malloc(sizeof(MyList)); + nums->capacity = 10; + nums->arr = malloc(sizeof(int) * nums->capacity); + nums->size = 0; + nums->extendRatio = 2; + return nums; +} + +/* デストラクタ */ +void delMyList(MyList *nums) { + free(nums->arr); + free(nums); +} + +/* リストの長さを取得 */ +int size(MyList *nums) { + return nums->size; +} + +/* リスト容量を取得する */ +int capacity(MyList *nums) { + return nums->capacity; +} + +/* 要素にアクセス */ +int get(MyList *nums, int index) { + assert(index >= 0 && index < nums->size); + return nums->arr[index]; +} + +/* 要素を更新 */ +void set(MyList *nums, int index, int num) { + assert(index >= 0 && index < nums->size); + nums->arr[index] = num; +} + +/* 末尾に要素を追加 */ +void add(MyList *nums, int num) { + if (size(nums) == capacity(nums)) { + extendCapacity(nums); // 容量を拡張 + } + nums->arr[size(nums)] = num; + nums->size++; +} + +/* 中間に要素を挿入 */ +void insert(MyList *nums, int index, int num) { + assert(index >= 0 && index < size(nums)); + // 要素数が容量を超えると、拡張機構が発動する + if (size(nums) == capacity(nums)) { + extendCapacity(nums); // 容量を拡張 + } + for (int i = size(nums); i > index; --i) { + nums->arr[i] = nums->arr[i - 1]; + } + nums->arr[index] = num; + nums->size++; +} + +/* 要素を削除 */ +// 注意: stdio.h が remove 識別子を使用している +int removeItem(MyList *nums, int index) { + assert(index >= 0 && index < size(nums)); + int num = nums->arr[index]; + for (int i = index; i < size(nums) - 1; i++) { + nums->arr[i] = nums->arr[i + 1]; + } + nums->size--; + return num; +} + +/* リストの拡張 */ +void extendCapacity(MyList *nums) { + // 先に領域を確保する + int newCapacity = capacity(nums) * nums->extendRatio; + int *extend = (int *)malloc(sizeof(int) * newCapacity); + int *temp = nums->arr; + + // 古いデータを新しいデータにコピー + for (int i = 0; i < size(nums); i++) + extend[i] = nums->arr[i]; + + // 古いデータを解放する + free(temp); + + // 新しいデータに更新 + nums->arr = extend; + nums->capacity = newCapacity; +} + +/* 出力用にリストを Array に変換 */ +int *toArray(MyList *nums) { + return nums->arr; +} + +/* Driver Code */ +int main() { + /* リストを初期化 */ + MyList *nums = newMyList(); + /* 末尾に要素を追加 */ + add(nums, 1); + add(nums, 3); + add(nums, 2); + add(nums, 5); + add(nums, 4); + printf("リスト nums = "); + printArray(toArray(nums), size(nums)); + printf("容量 = %d ,長さ = %d\n", capacity(nums), size(nums)); + + /* 中間に要素を挿入 */ + insert(nums, 3, 6); + printf("インデックス 3 に数字 6 を挿入し、nums = "); + printArray(toArray(nums), size(nums)); + + /* 要素を削除 */ + removeItem(nums, 3); + printf("インデックス 3 の要素を削除し、nums = "); + printArray(toArray(nums), size(nums)); + + /* 要素にアクセス */ + int num = get(nums, 1); + printf("インデックス 1 の要素にアクセスし、num = %d\n", num); + + /* 要素を更新 */ + set(nums, 1, 0); + printf("インデックス 1 の要素を 0 に更新し、nums = "); + printArray(toArray(nums), size(nums)); + + /* 拡張機構をテストする */ + for (int i = 0; i < 10; i++) { + // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する + add(nums, i); + } + + printf("拡張後のリスト nums = "); + printArray(toArray(nums), size(nums)); + printf("容量 = %d ,長さ = %d\n", capacity(nums), size(nums)); + + /* 確保したメモリを解放する */ + delMyList(nums); + + return 0; +} diff --git a/ja/codes/c/chapter_backtracking/CMakeLists.txt b/ja/codes/c/chapter_backtracking/CMakeLists.txt new file mode 100644 index 000000000..70161b6dd --- /dev/null +++ b/ja/codes/c/chapter_backtracking/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(permutations_i permutations_i.c) +add_executable(permutations_ii permutations_ii.c) +add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.c) +add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.c) +add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.c) +add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.c) +add_executable(subset_sum_i_naive subset_sum_i_naive.c) +add_executable(subset_sum_i subset_sum_i.c) +add_executable(subset_sum_ii subset_sum_ii.c) +add_executable(n_queens n_queens.c) \ No newline at end of file diff --git a/ja/codes/c/chapter_backtracking/n_queens.c b/ja/codes/c/chapter_backtracking/n_queens.c new file mode 100644 index 000000000..e81e9ca47 --- /dev/null +++ b/ja/codes/c/chapter_backtracking/n_queens.c @@ -0,0 +1,95 @@ +/** + * File : n_queens.c + * Created Time: 2023-09-25 + * Author : lucas (superrat6@gmail.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 + +/* バックトラッキング:N クイーン */ +void backtrack(int row, int n, char state[MAX_SIZE][MAX_SIZE], char ***res, int *resSize, bool cols[MAX_SIZE], + bool diags1[2 * MAX_SIZE - 1], bool diags2[2 * MAX_SIZE - 1]) { + // すべての行への配置が完了したら、解を記録する + if (row == n) { + res[*resSize] = (char **)malloc(sizeof(char *) * n); + for (int i = 0; i < n; ++i) { + res[*resSize][i] = (char *)malloc(sizeof(char) * (n + 1)); + strcpy(res[*resSize][i], state[i]); + } + (*resSize)++; + return; + } + // すべての列を走査 + for (int col = 0; col < n; col++) { + // このマスに対応する主対角線と副対角線を計算 + int diag1 = row - col + n - 1; + int 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, resSize, cols, diags1, diags2); + // 戻す:そのマスを空きマスに戻す + state[row][col] = '#'; + cols[col] = diags1[diag1] = diags2[diag2] = false; + } + } +} + +/* N クイーンを解く */ +char ***nQueens(int n, int *returnSize) { + char state[MAX_SIZE][MAX_SIZE]; + // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す + for (int i = 0; i < n; ++i) { + for (int j = 0; j < n; ++j) { + state[i][j] = '#'; + } + state[i][n] = '\0'; + } + bool cols[MAX_SIZE] = {false}; // 列にクイーンがあるか記録 + bool diags1[2 * MAX_SIZE - 1] = {false}; // 主対角線にクイーンがあるかを記録 + bool diags2[2 * MAX_SIZE - 1] = {false}; // 副対角線にクイーンがあるかを記録 + + char ***res = (char ***)malloc(sizeof(char **) * MAX_SIZE); + *returnSize = 0; + backtrack(0, n, state, res, returnSize, cols, diags1, diags2); + return res; +} + +/* Driver Code */ +int main() { + int n = 4; + int returnSize; + char ***res = nQueens(n, &returnSize); + + printf("盤面の縦横は%d\n", n); + printf("クイーンの配置方法は全部で %d 通り\n", returnSize); + for (int i = 0; i < returnSize; ++i) { + for (int j = 0; j < n; ++j) { + printf("["); + for (int k = 0; res[i][j][k] != '\0'; ++k) { + printf("%c", res[i][j][k]); + if (res[i][j][k + 1] != '\0') { + printf(", "); + } + } + printf("]\n"); + } + printf("---------------------\n"); + } + + // メモリを解放する + for (int i = 0; i < returnSize; ++i) { + for (int j = 0; j < n; ++j) { + free(res[i][j]); + } + free(res[i]); + } + free(res); + + return 0; +} diff --git a/ja/codes/c/chapter_backtracking/permutations_i.c b/ja/codes/c/chapter_backtracking/permutations_i.c new file mode 100644 index 000000000..6fc655760 --- /dev/null +++ b/ja/codes/c/chapter_backtracking/permutations_i.c @@ -0,0 +1,79 @@ +/** + * File: permutations_i.c + * Created Time: 2023-06-04 + * Author: Gonglja (glj0@outlook.com), krahets (krahets@163.com) + */ + +#include "../utils/common.h" + +// 順列は最大 1000 個と仮定 +#define MAX_SIZE 1000 + +/* バックトラッキング:順列 I */ +void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { + // 状態の長さが要素数に等しければ、解を記録 + if (stateSize == choicesSize) { + res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); + for (int i = 0; i < choicesSize; i++) { + res[*resSize][i] = state[i]; + } + (*resSize)++; + return; + } + // すべての選択肢を走査 + for (int i = 0; i < choicesSize; i++) { + int choice = choices[i]; + // 枝刈り:要素の重複選択を許可しない + if (!selected[i]) { + // 試行: 選択を行い、状態を更新 + selected[i] = true; + state[stateSize] = choice; + // 次の選択へ進む + backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); + // バックトラック:選択を取り消し、前の状態に戻す + selected[i] = false; + } + } +} + +/* 全順列 I */ +int **permutationsI(int *nums, int numsSize, int *returnSize) { + int *state = (int *)malloc(numsSize * sizeof(int)); + bool *selected = (bool *)malloc(numsSize * sizeof(bool)); + for (int i = 0; i < numsSize; i++) { + selected[i] = false; + } + int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); + *returnSize = 0; + + backtrack(state, 0, nums, numsSize, selected, res, returnSize); + + free(state); + free(selected); + + return res; +} + +/* Driver Code */ +int main() { + int nums[] = {1, 2, 3}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int returnSize; + + int **res = permutationsI(nums, numsSize, &returnSize); + + printf("入力配列 nums = "); + printArray(nums, numsSize); + printf("\nすべての順列 res = \n"); + for (int i = 0; i < returnSize; i++) { + printArray(res[i], numsSize); + } + + // メモリを解放する + for (int i = 0; i < returnSize; i++) { + free(res[i]); + } + free(res); + + return 0; +} diff --git a/ja/codes/c/chapter_backtracking/permutations_ii.c b/ja/codes/c/chapter_backtracking/permutations_ii.c new file mode 100644 index 000000000..65a637bef --- /dev/null +++ b/ja/codes/c/chapter_backtracking/permutations_ii.c @@ -0,0 +1,81 @@ +/** + * File: permutations_ii.c + * Created Time: 2023-10-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.h" + +// 順列は最大 1000 個、要素の最大値は 1000 と仮定する +#define MAX_SIZE 1000 + +/* バックトラッキング:順列 II */ +void backtrack(int *state, int stateSize, int *choices, int choicesSize, bool *selected, int **res, int *resSize) { + // 状態の長さが要素数に等しければ、解を記録 + if (stateSize == choicesSize) { + res[*resSize] = (int *)malloc(choicesSize * sizeof(int)); + for (int i = 0; i < choicesSize; i++) { + res[*resSize][i] = state[i]; + } + (*resSize)++; + return; + } + // すべての選択肢を走査 + bool duplicated[MAX_SIZE] = {false}; + for (int i = 0; i < choicesSize; i++) { + int choice = choices[i]; + // 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない + if (!selected[i] && !duplicated[choice]) { + // 試行: 選択を行い、状態を更新 + duplicated[choice] = true; // 選択済みの要素値を記録 + selected[i] = true; + state[stateSize] = choice; + // 次の選択へ進む + backtrack(state, stateSize + 1, choices, choicesSize, selected, res, resSize); + // バックトラック:選択を取り消し、前の状態に戻す + selected[i] = false; + } + } +} + +/* 全順列 II */ +int **permutationsII(int *nums, int numsSize, int *returnSize) { + int *state = (int *)malloc(numsSize * sizeof(int)); + bool *selected = (bool *)malloc(numsSize * sizeof(bool)); + for (int i = 0; i < numsSize; i++) { + selected[i] = false; + } + int **res = (int **)malloc(MAX_SIZE * sizeof(int *)); + *returnSize = 0; + + backtrack(state, 0, nums, numsSize, selected, res, returnSize); + + free(state); + free(selected); + + return res; +} + +/* Driver Code */ +int main() { + int nums[] = {1, 1, 2}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int returnSize; + + int **res = permutationsII(nums, numsSize, &returnSize); + + printf("入力配列 nums = "); + printArray(nums, numsSize); + printf("\nすべての順列 res = \n"); + for (int i = 0; i < returnSize; i++) { + printArray(res[i], numsSize); + } + + // メモリを解放する + for (int i = 0; i < returnSize; i++) { + free(res[i]); + } + free(res); + + return 0; +} diff --git a/ja/codes/c/chapter_backtracking/preorder_traversal_i_compact.c b/ja/codes/c/chapter_backtracking/preorder_traversal_i_compact.c new file mode 100644 index 000000000..9ceb1d071 --- /dev/null +++ b/ja/codes/c/chapter_backtracking/preorder_traversal_i_compact.c @@ -0,0 +1,49 @@ +/** + * File: preorder_traversal_i_compact.c + * Created Time: 2023-05-10 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +// 結果の長さは 100 を超えないと仮定する +#define MAX_SIZE 100 + +TreeNode *res[MAX_SIZE]; +int resSize = 0; + +/* 前順走査:例題 1 */ +void preOrder(TreeNode *root) { + if (root == NULL) { + return; + } + if (root->val == 7) { + // 解を記録 + res[resSize++] = root; + } + preOrder(root->left); + preOrder(root->right); +} + +/* Driver Code */ +int main() { + int arr[] = {1, 7, 3, 4, 5, 6, 7}; + TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); + printf("\n二分木を初期化\n"); + printTree(root); + + // 先行順走査 + preOrder(root); + + printf("\n値が 7 のすべてのノードを出力\n"); + int *vals = malloc(resSize * sizeof(int)); + for (int i = 0; i < resSize; i++) { + vals[i] = res[i]->val; + } + printArray(vals, resSize); + + // メモリを解放する + freeMemoryTree(root); + free(vals); + return 0; +} diff --git a/ja/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c b/ja/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c new file mode 100644 index 000000000..ca073be55 --- /dev/null +++ b/ja/codes/c/chapter_backtracking/preorder_traversal_ii_compact.c @@ -0,0 +1,61 @@ +/** + * File: preorder_traversal_ii_compact.c + * Created Time: 2023-05-28 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +// パスと結果の長さは 100 以下と仮定 +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +TreeNode *path[MAX_SIZE]; +TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; +int pathSize = 0, resSize = 0; + +/* 前順走査:例題 2 */ +void preOrder(TreeNode *root) { + if (root == NULL) { + return; + } + // 試す + path[pathSize++] = root; + if (root->val == 7) { + // 解を記録 + for (int i = 0; i < pathSize; ++i) { + res[resSize][i] = path[i]; + } + resSize++; + } + preOrder(root->left); + preOrder(root->right); + // バックトラック + pathSize--; +} + +/* Driver Code */ +int main() { + int arr[] = {1, 7, 3, 4, 5, 6, 7}; + TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); + printf("\n二分木を初期化\n"); + printTree(root); + + // 先行順走査 + preOrder(root); + + printf("\nルートノードからノード 7 までのすべての経路を出力\n"); + for (int i = 0; i < resSize; ++i) { + int *vals = malloc(MAX_SIZE * sizeof(int)); + int size = 0; + for (int j = 0; res[i][j] != NULL; ++j) { + vals[size++] = res[i][j]->val; + } + printArray(vals, size); + free(vals); + } + + // メモリを解放する + freeMemoryTree(root); + return 0; +} diff --git a/ja/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c b/ja/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c new file mode 100644 index 000000000..b527249c0 --- /dev/null +++ b/ja/codes/c/chapter_backtracking/preorder_traversal_iii_compact.c @@ -0,0 +1,62 @@ +/** + * File: preorder_traversal_iii_compact.c + * Created Time: 2023-06-04 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +// パスと結果の長さは 100 以下と仮定 +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +TreeNode *path[MAX_SIZE]; +TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; +int pathSize = 0, resSize = 0; + +/* 前順走査:例題 3 */ +void preOrder(TreeNode *root) { + // 枝刈り + if (root == NULL || root->val == 3) { + return; + } + // 試す + path[pathSize++] = root; + if (root->val == 7) { + // 解を記録 + for (int i = 0; i < pathSize; i++) { + res[resSize][i] = path[i]; + } + resSize++; + } + preOrder(root->left); + preOrder(root->right); + // バックトラック + pathSize--; +} + +/* Driver Code */ +int main() { + int arr[] = {1, 7, 3, 4, 5, 6, 7}; + TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); + printf("\n二分木を初期化\n"); + printTree(root); + + // 先行順走査 + preOrder(root); + + printf("\nルートノードからノード 7 までのすべての経路を出力し、経路に値が 3 のノードを含めない\n"); + for (int i = 0; i < resSize; ++i) { + int *vals = malloc(MAX_SIZE * sizeof(int)); + int size = 0; + for (int j = 0; res[i][j] != NULL; ++j) { + vals[size++] = res[i][j]->val; + } + printArray(vals, size); + free(vals); + } + + // メモリを解放する + freeMemoryTree(root); + return 0; +} diff --git a/ja/codes/c/chapter_backtracking/preorder_traversal_iii_template.c b/ja/codes/c/chapter_backtracking/preorder_traversal_iii_template.c new file mode 100644 index 000000000..b8343cc7c --- /dev/null +++ b/ja/codes/c/chapter_backtracking/preorder_traversal_iii_template.c @@ -0,0 +1,93 @@ +/** + * File: preorder_traversal_iii_template.c + * Created Time: 2023-06-04 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +// パスと結果の長さは 100 以下と仮定 +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +TreeNode *path[MAX_SIZE]; +TreeNode *res[MAX_RES_SIZE][MAX_SIZE]; +int pathSize = 0, resSize = 0; + +/* 現在の状態が解かどうかを判定 */ +bool isSolution(void) { + return pathSize > 0 && path[pathSize - 1]->val == 7; +} + +/* 解を記録 */ +void recordSolution(void) { + for (int i = 0; i < pathSize; i++) { + res[resSize][i] = path[i]; + } + resSize++; +} + +/* 現在の状態で、この選択が有効かどうかを判定 */ +bool isValid(TreeNode *choice) { + return choice != NULL && choice->val != 3; +} + +/* 状態を更新 */ +void makeChoice(TreeNode *choice) { + path[pathSize++] = choice; +} + +/* 状態を元に戻す */ +void undoChoice(void) { + pathSize--; +} + +/* バックトラッキング:例題 3 */ +void backtrack(TreeNode *choices[2]) { + // 解かどうかを確認 + if (isSolution()) { + // 解を記録 + recordSolution(); + } + // すべての選択肢を走査 + for (int i = 0; i < 2; i++) { + TreeNode *choice = choices[i]; + // 枝刈り:選択が妥当かを確認する + if (isValid(choice)) { + // 試行: 選択を行い、状態を更新 + makeChoice(choice); + // 次の選択へ進む + TreeNode *nextChoices[2] = {choice->left, choice->right}; + backtrack(nextChoices); + // バックトラック:選択を取り消し、前の状態に戻す + undoChoice(); + } + } +} + +/* Driver Code */ +int main() { + int arr[] = {1, 7, 3, 4, 5, 6, 7}; + TreeNode *root = arrayToTree(arr, sizeof(arr) / sizeof(arr[0])); + printf("\n二分木を初期化\n"); + printTree(root); + + // バックトラッキング法 + TreeNode *choices[2] = {root, NULL}; + backtrack(choices); + + printf("\nルートノードからノード 7 までのすべての経路を出力し、経路に値が 3 のノードを含めない\n"); + for (int i = 0; i < resSize; ++i) { + int *vals = malloc(MAX_SIZE * sizeof(int)); + int size = 0; + for (int j = 0; res[i][j] != NULL; ++j) { + vals[size++] = res[i][j]->val; + } + printArray(vals, size); + free(vals); + } + + // メモリを解放する + freeMemoryTree(root); + return 0; +} diff --git a/ja/codes/c/chapter_backtracking/subset_sum_i.c b/ja/codes/c/chapter_backtracking/subset_sum_i.c new file mode 100644 index 000000000..2cfe002bd --- /dev/null +++ b/ja/codes/c/chapter_backtracking/subset_sum_i.c @@ -0,0 +1,78 @@ +/** + * File: subset_sum_i.c + * Created Time: 2023-07-29 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +// 状態(部分集合) +int state[MAX_SIZE]; +int stateSize = 0; + +// 結果リスト(部分集合のリスト) +int res[MAX_RES_SIZE][MAX_SIZE]; +int resColSizes[MAX_RES_SIZE]; +int resSize = 0; + +/* バックトラッキング:部分和 I */ +void backtrack(int target, int *choices, int choicesSize, int start) { + // 部分集合の和が target に等しければ、解を記録 + if (target == 0) { + for (int i = 0; i < stateSize; ++i) { + res[resSize][i] = state[i]; + } + resColSizes[resSize++] = stateSize; + return; + } + // すべての選択肢を走査 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + for (int i = start; i < choicesSize; i++) { + // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため + if (target - choices[i] < 0) { + break; + } + // 試す:選択を行い、target と start を更新 + state[stateSize] = choices[i]; + stateSize++; + // 次の選択へ進む + backtrack(target - choices[i], choices, choicesSize, i); + // バックトラック:選択を取り消し、前の状態に戻す + stateSize--; + } +} + +/* 比較関数 */ +int cmp(const void *a, const void *b) { + return (*(int *)a - *(int *)b); +} + +/* 部分和 I を解く */ +void subsetSumI(int *nums, int numsSize, int target) { + qsort(nums, numsSize, sizeof(int), cmp); // nums をソート + int start = 0; // 開始点を走査 + backtrack(target, nums, numsSize, start); +} + +/* Driver Code */ +int main() { + int nums[] = {3, 4, 5}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int target = 9; + + subsetSumI(nums, numsSize, target); + + printf("入力配列 nums = "); + printArray(nums, numsSize); + printf("target = %d\n", target); + printf("合計が %d に等しいすべての部分集合 res = \n", target); + for (int i = 0; i < resSize; ++i) { + printArray(res[i], resColSizes[i]); + } + + return 0; +} diff --git a/ja/codes/c/chapter_backtracking/subset_sum_i_naive.c b/ja/codes/c/chapter_backtracking/subset_sum_i_naive.c new file mode 100644 index 000000000..d2d0c2033 --- /dev/null +++ b/ja/codes/c/chapter_backtracking/subset_sum_i_naive.c @@ -0,0 +1,69 @@ +/** + * File: subset_sum_i_naive.c + * Created Time: 2023-07-28 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +// 状態(部分集合) +int state[MAX_SIZE]; +int stateSize = 0; + +// 結果リスト(部分集合のリスト) +int res[MAX_RES_SIZE][MAX_SIZE]; +int resColSizes[MAX_RES_SIZE]; +int resSize = 0; + +/* バックトラッキング:部分和 I */ +void backtrack(int target, int total, int *choices, int choicesSize) { + // 部分集合の和が target に等しければ、解を記録 + if (total == target) { + for (int i = 0; i < stateSize; i++) { + res[resSize][i] = state[i]; + } + resColSizes[resSize++] = stateSize; + return; + } + // すべての選択肢を走査 + for (int i = 0; i < choicesSize; i++) { + // 枝刈り:部分和が target を超える場合はその選択をスキップする + if (total + choices[i] > target) { + continue; + } + // 試行:選択を行い、要素と total を更新する + state[stateSize++] = choices[i]; + // 次の選択へ進む + backtrack(target, total + choices[i], choices, choicesSize); + // バックトラック:選択を取り消し、前の状態に戻す + stateSize--; + } +} + +/* 部分和 I を解く(重複部分集合を含む) */ +void subsetSumINaive(int *nums, int numsSize, int target) { + resSize = 0; // 解の個数を 0 に初期化する + backtrack(target, 0, nums, numsSize); +} + +/* Driver Code */ +int main() { + int nums[] = {3, 4, 5}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int target = 9; + + subsetSumINaive(nums, numsSize, target); + + printf("入力配列 nums = "); + printArray(nums, numsSize); + printf("target = %d\n", target); + printf("合計が %d に等しいすべての部分集合 res = \n", target); + for (int i = 0; i < resSize; i++) { + printArray(res[i], resColSizes[i]); + } + + return 0; +} diff --git a/ja/codes/c/chapter_backtracking/subset_sum_ii.c b/ja/codes/c/chapter_backtracking/subset_sum_ii.c new file mode 100644 index 000000000..e893b13e8 --- /dev/null +++ b/ja/codes/c/chapter_backtracking/subset_sum_ii.c @@ -0,0 +1,83 @@ +/** + * File: subset_sum_ii.c + * Created Time: 2023-07-29 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 +#define MAX_RES_SIZE 100 + +// 状態(部分集合) +int state[MAX_SIZE]; +int stateSize = 0; + +// 結果リスト(部分集合のリスト) +int res[MAX_RES_SIZE][MAX_SIZE]; +int resColSizes[MAX_RES_SIZE]; +int resSize = 0; + +/* バックトラッキング:部分和 II */ +void backtrack(int target, int *choices, int choicesSize, int start) { + // 部分集合の和が target に等しければ、解を記録 + if (target == 0) { + for (int i = 0; i < stateSize; i++) { + res[resSize][i] = state[i]; + } + resColSizes[resSize++] = stateSize; + return; + } + // すべての選択肢を走査 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける + for (int i = start; i < choicesSize; i++) { + // 枝刈り 1: 部分集合の和が target を超えたら、そのままスキップする + if (target - choices[i] < 0) { + continue; + } + // 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // 試す:選択を行い、target と start を更新 + state[stateSize] = choices[i]; + stateSize++; + // 次の選択へ進む + backtrack(target - choices[i], choices, choicesSize, i + 1); + // バックトラック:選択を取り消し、前の状態に戻す + stateSize--; + } +} + +/* 比較関数 */ +int cmp(const void *a, const void *b) { + return (*(int *)a - *(int *)b); +} + +/* 部分和 II を解く */ +void subsetSumII(int *nums, int numsSize, int target) { + // nums をソート + qsort(nums, numsSize, sizeof(int), cmp); + // バックトラッキングを開始 + backtrack(target, nums, numsSize, 0); +} + +/* Driver Code */ +int main() { + int nums[] = {4, 4, 5}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + int target = 9; + + subsetSumII(nums, numsSize, target); + + printf("入力配列 nums = "); + printArray(nums, numsSize); + printf("target = %d\n", target); + printf("合計が %d に等しいすべての部分集合 res = \n", target); + for (int i = 0; i < resSize; ++i) { + printArray(res[i], resColSizes[i]); + } + + return 0; +} diff --git a/ja/codes/c/chapter_computational_complexity/CMakeLists.txt b/ja/codes/c/chapter_computational_complexity/CMakeLists.txt new file mode 100644 index 000000000..dcfa063c3 --- /dev/null +++ b/ja/codes/c/chapter_computational_complexity/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(iteration iteration.c) +add_executable(recursion recursion.c) +add_executable(time_complexity time_complexity.c) +add_executable(worst_best_time_complexity worst_best_time_complexity.c) +add_executable(space_complexity space_complexity.c) diff --git a/ja/codes/c/chapter_computational_complexity/iteration.c b/ja/codes/c/chapter_computational_complexity/iteration.c new file mode 100644 index 000000000..09d2ef9e4 --- /dev/null +++ b/ja/codes/c/chapter_computational_complexity/iteration.c @@ -0,0 +1,81 @@ +/** + * File: iteration.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com), MwumLi (mwumli@hotmail.com) + */ + +#include "../utils/common.h" + +/* for ループ */ +int forLoop(int n) { + int res = 0; + // 1, 2, ..., n-1, n を順に加算する + for (int i = 1; i <= n; i++) { + res += i; + } + return res; +} + +/* while ループ */ +int whileLoop(int n) { + int res = 0; + int i = 1; // 条件変数を初期化する + // 1, 2, ..., n-1, n を順に加算する + while (i <= n) { + res += i; + i++; // 条件変数を更新する + } + return res; +} + +/* while ループ(2回更新) */ +int whileLoopII(int n) { + int res = 0; + int i = 1; // 条件変数を初期化する + // 1, 4, 10, ... を順に加算する + while (i <= n) { + res += i; + // 条件変数を更新する + i++; + i *= 2; + } + return res; +} + +/* 二重 for ループ */ +char *nestedForLoop(int n) { + // n * n は対応する点の個数であり、"(i, j), " に対応する文字列長の最大は 6+10*2 で、さらに末尾の空文字 \0 のための追加領域が必要 + int size = n * n * 26 + 1; + char *res = malloc(size * sizeof(char)); + // i = 1, 2, ..., n-1, n とループする + for (int i = 1; i <= n; i++) { + // j = 1, 2, ..., n-1, n とループする + for (int j = 1; j <= n; j++) { + char tmp[26]; + snprintf(tmp, sizeof(tmp), "(%d, %d), ", i, j); + strncat(res, tmp, size - strlen(res) - 1); + } + } + return res; +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = forLoop(n); + printf("\nfor ループの合計結果 res = %d\n", res); + + res = whileLoop(n); + printf("\nwhile ループの合計結果 res = %d\n", res); + + res = whileLoopII(n); + printf("\nwhile ループ(2回更新)の合計結果 res = %d\n", res); + + char *resStr = nestedForLoop(n); + printf("\n二重 for ループの走査結果 %s\r\n", resStr); + free(resStr); + + return 0; +} diff --git a/ja/codes/c/chapter_computational_complexity/recursion.c b/ja/codes/c/chapter_computational_complexity/recursion.c new file mode 100644 index 000000000..4a6575b11 --- /dev/null +++ b/ja/codes/c/chapter_computational_complexity/recursion.c @@ -0,0 +1,77 @@ +/** + * File: recursion.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 再帰 */ +int recur(int n) { + // 終了条件 + if (n == 1) + return 1; + // 再帰:再帰呼び出し + int res = recur(n - 1); + // 帰りがけ:結果を返す + return n + res; +} + +/* 反復で再帰を模擬する */ +int forLoopRecur(int n) { + int stack[1000]; // 大きな配列を使ってスタックを実装する + int top = -1; // スタックトップのインデックス + int res = 0; + // 再帰:再帰呼び出し + for (int i = n; i > 0; i--) { + // 「スタックへのプッシュ」で「再帰」を模擬する + stack[1 + top++] = i; + } + // 帰りがけ:結果を返す + while (top >= 0) { + // 「スタックから取り出す操作」で「帰り」をシミュレート + res += stack[top--]; + } + // res = 1+2+3+...+n + return res; +} + +/* 末尾再帰 */ +int tailRecur(int n, int res) { + // 終了条件 + if (n == 0) + return res; + // 末尾再帰呼び出し + return tailRecur(n - 1, res + n); +} + +/* フィボナッチ数列:再帰 */ +int fib(int n) { + // 終了条件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す + int res = fib(n - 1) + fib(n - 2); + // 結果 f(n) を返す + return res; +} + +/* Driver Code */ +int main() { + int n = 5; + int res; + + res = recur(n); + printf("\n再帰関数の合計結果 res = %d\n", res); + + res = forLoopRecur(n); + printf("\n反復で再帰をシミュレートした合計結果 res = %d\n", res); + + res = tailRecur(n, 0); + printf("\n末尾再帰関数の合計結果 res = %d\n", res); + + res = fib(n); + printf("\nフィボナッチ数列の第 %d 項は %d\n", n, res); + + return 0; +} diff --git a/ja/codes/c/chapter_computational_complexity/space_complexity.c b/ja/codes/c/chapter_computational_complexity/space_complexity.c new file mode 100644 index 000000000..a93d54438 --- /dev/null +++ b/ja/codes/c/chapter_computational_complexity/space_complexity.c @@ -0,0 +1,141 @@ +/** + * File: space_complexity.c + * Created Time: 2023-04-15 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 関数 */ +int func() { + // 何らかの処理を行う + return 0; +} + +/* 定数階 */ +void constant(int n) { + // 定数、変数、オブジェクトは O(1) の空間を占める + const int a = 0; + int b = 0; + int nums[1000]; + ListNode *node = newListNode(0); + free(node); + // ループ内の変数は O(1) の空間を占める + for (int i = 0; i < n; i++) { + int c = 0; + } + // ループ内の関数は O(1) の空間を占める + for (int i = 0; i < n; i++) { + func(); + } +} + +/* ハッシュテーブル */ +typedef struct { + int key; + int val; + UT_hash_handle hh; // uthash.h を用いて実装 +} HashTable; + +/* 線形階 */ +void linear(int n) { + // 長さ n の配列は O(n) の空間を使用 + int *nums = malloc(sizeof(int) * n); + free(nums); + + // 長さ n のリストは O(n) の空間を使用 + ListNode **nodes = malloc(sizeof(ListNode *) * n); + for (int i = 0; i < n; i++) { + nodes[i] = newListNode(i); + } + // メモリを解放する + for (int i = 0; i < n; i++) { + free(nodes[i]); + } + free(nodes); + + // 長さ n のハッシュテーブルは O(n) の空間を使用 + HashTable *h = NULL; + for (int i = 0; i < n; i++) { + HashTable *tmp = malloc(sizeof(HashTable)); + tmp->key = i; + tmp->val = i; + HASH_ADD_INT(h, key, tmp); + } + + // メモリを解放する + HashTable *curr, *tmp; + HASH_ITER(hh, h, curr, tmp) { + HASH_DEL(h, curr); + free(curr); + } +} + +/* 線形時間(再帰実装) */ +void linearRecur(int n) { + printf("再帰 n = %d\r\n", n); + if (n == 1) + return; + linearRecur(n - 1); +} + +/* 二乗階 */ +void quadratic(int n) { + // 二次元リストは O(n^2) の空間を使用 + int **numMatrix = malloc(sizeof(int *) * n); + for (int i = 0; i < n; i++) { + int *tmp = malloc(sizeof(int) * n); + for (int j = 0; j < n; j++) { + tmp[j] = 0; + } + numMatrix[i] = tmp; + } + + // メモリを解放する + for (int i = 0; i < n; i++) { + free(numMatrix[i]); + } + free(numMatrix); +} + +/* 二次時間(再帰実装) */ +int quadraticRecur(int n) { + if (n <= 0) + return 0; + int *nums = malloc(sizeof(int) * n); + printf("再帰 n = %d における nums の長さ = %d\r\n", n, n); + int res = quadraticRecur(n - 1); + free(nums); + return res; +} + +/* 指数時間(完全二分木の構築) */ +TreeNode *buildTree(int n) { + if (n == 0) + return NULL; + TreeNode *root = newTreeNode(0); + root->left = buildTree(n - 1); + root->right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +int main() { + int n = 5; + // 定数階 + constant(n); + // 線形階 + linear(n); + linearRecur(n); + // 二乗階 + quadratic(n); + quadraticRecur(n); + // 指数オーダー + TreeNode *root = buildTree(n); + printTree(root); + + // メモリを解放する + freeMemoryTree(root); + + return 0; +} diff --git a/ja/codes/c/chapter_computational_complexity/time_complexity.c b/ja/codes/c/chapter_computational_complexity/time_complexity.c new file mode 100644 index 000000000..ff4c9c00d --- /dev/null +++ b/ja/codes/c/chapter_computational_complexity/time_complexity.c @@ -0,0 +1,179 @@ +/** + * File: time_complexity.c + * Created Time: 2023-01-03 + * Author: codingonion (coderonion@gmail.com) + */ + +#include "../utils/common.h" + +/* 定数階 */ +int constant(int n) { + int count = 0; + int size = 100000; + int i = 0; + for (int i = 0; i < size; i++) { + count++; + } + return count; +} + +/* 線形階 */ +int linear(int n) { + int count = 0; + for (int i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 線形時間(配列を走査) */ +int arrayTraversal(int *nums, int n) { + int count = 0; + // ループ回数は配列長に比例する + for (int i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 二乗階 */ +int quadratic(int n) { + int count = 0; + // ループ回数はデータサイズ n の二乗に比例する + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* 二次時間(バブルソート) */ +int bubbleSort(int *nums, int n) { + int count = 0; // カウンタ + // 外側のループ:未ソート区間は [0, i] + for (int i = n - 1; i > 0; i--) { + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // nums[j] と nums[j + 1] を交換 + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 要素交換には 3 回の単位操作が含まれる + } + } + } + return count; +} + +/* 指数時間(ループ実装) */ +int exponential(int n) { + int count = 0; + int bas = 1; + // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する + for (int i = 0; i < n; i++) { + for (int j = 0; j < bas; j++) { + count++; + } + bas *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* 指数時間(再帰実装) */ +int expRecur(int n) { + if (n == 1) + return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* 対数時間(ループ実装) */ +int logarithmic(int n) { + int count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; +} + +/* 対数時間(再帰実装) */ +int logRecur(int n) { + if (n <= 1) + return 0; + return logRecur(n / 2) + 1; +} + +/* 線形対数時間 */ +int linearLogRecur(int n) { + if (n <= 1) + return 1; + int count = linearLogRecur(n / 2) + linearLogRecur(n / 2); + for (int i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 階乗時間(再帰実装) */ +int factorialRecur(int n) { + if (n == 0) + return 1; + int count = 0; + for (int i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +int main(int argc, char *argv[]) { + // n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる + int n = 8; + printf("入力データサイズ n = %d\n", n); + + int count = constant(n); + printf("定数オーダーの操作回数 = %d\n", count); + + count = linear(n); + printf("線形オーダーの操作回数 = %d\n", count); + // ヒープ領域にメモリを確保する(要素数 n、要素型 int の一次元可変長配列を作成) + int *nums = (int *)malloc(n * sizeof(int)); + count = arrayTraversal(nums, n); + printf("線形オーダー(配列の走査)の操作回数 = %d\n", count); + + count = quadratic(n); + printf("平方オーダーの操作回数 = %d\n", count); + for (int i = 0; i < n; i++) { + nums[i] = n - i; // [n,n-1,...,2,1] + } + count = bubbleSort(nums, n); + printf("平方オーダー(バブルソート)の操作回数 = %d\n", count); + + count = exponential(n); + printf("指数オーダー(ループ実装)の操作回数 = %d\n", count); + count = expRecur(n); + printf("指数オーダー(再帰実装)の操作回数 = %d\n", count); + + count = logarithmic(n); + printf("対数オーダー(ループ実装)の操作回数 = %d\n", count); + count = logRecur(n); + printf("対数オーダー(再帰実装)の操作回数 = %d\n", count); + + count = linearLogRecur(n); + printf("線形対数オーダー(再帰実装)の操作回数 = %d\n", count); + + count = factorialRecur(n); + printf("階乗オーダー(再帰実装)の操作回数 = %d\n", count); + + // ヒープ領域のメモリを解放 + if (nums != NULL) { + free(nums); + nums = NULL; + } + getchar(); + + return 0; +} diff --git a/ja/codes/c/chapter_computational_complexity/worst_best_time_complexity.c b/ja/codes/c/chapter_computational_complexity/worst_best_time_complexity.c new file mode 100644 index 000000000..6ef87eb3f --- /dev/null +++ b/ja/codes/c/chapter_computational_complexity/worst_best_time_complexity.c @@ -0,0 +1,57 @@ +/** + * File: worst_best_time_complexity.c + * Created Time: 2023-01-03 + * Author: codingonion (coderonion@gmail.com) + */ + +#include "../utils/common.h" + +/* 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 */ +int *randomNumbers(int n) { + // ヒープ領域にメモリを確保する(要素数 n、要素型 int の一次元可変長配列を作成) + int *nums = (int *)malloc(n * sizeof(int)); + // 配列 nums = { 1, 2, 3, ..., n } を生成 + for (int i = 0; i < n; i++) { + nums[i] = i + 1; + } + // 配列要素をランダムにシャッフル + for (int i = n - 1; i > 0; i--) { + int j = rand() % (i + 1); + int temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; + } + return nums; +} + +/* 配列 nums 内で数値 1 のインデックスを探す */ +int findOne(int *nums, int n) { + for (int i = 0; i < n; i++) { + // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる + // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる + if (nums[i] == 1) + return i; + } + return -1; +} + +/* Driver Code */ +int main(int argc, char *argv[]) { + // 乱数シードを初期化する + srand((unsigned int)time(NULL)); + for (int i = 0; i < 10; i++) { + int n = 100; + int *nums = randomNumbers(n); + int index = findOne(nums, n); + printf("\n配列 [ 1, 2, ..., n ] をシャッフルした後 = "); + printArray(nums, n); + printf("数値 1 のインデックスは %d\n", index); + // ヒープ領域のメモリを解放 + if (nums != NULL) { + free(nums); + nums = NULL; + } + } + + return 0; +} diff --git a/ja/codes/c/chapter_divide_and_conquer/CMakeLists.txt b/ja/codes/c/chapter_divide_and_conquer/CMakeLists.txt new file mode 100644 index 000000000..e03b1c588 --- /dev/null +++ b/ja/codes/c/chapter_divide_and_conquer/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(binary_search_recur binary_search_recur.c) +add_executable(build_tree build_tree.c) +add_executable(hanota hanota.c) diff --git a/ja/codes/c/chapter_divide_and_conquer/binary_search_recur.c b/ja/codes/c/chapter_divide_and_conquer/binary_search_recur.c new file mode 100644 index 000000000..646685a74 --- /dev/null +++ b/ja/codes/c/chapter_divide_and_conquer/binary_search_recur.c @@ -0,0 +1,47 @@ +/** + * File: binary_search_recur.c + * Created Time: 2023-10-01 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 二分探索:問題 f(i, j) */ +int dfs(int nums[], int target, int i, int j) { + // 区間が空なら対象要素は存在しないので -1 を返す + if (i > j) { + return -1; + } + // 中点インデックス m を計算 + int m = (i + j) / 2; + if (nums[m] < target) { + // 部分問題 f(m+1, j) を再帰的に解く + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // 部分問題 f(i, m-1) を再帰的に解く + return dfs(nums, target, i, m - 1); + } else { + // 目標要素が見つかったらそのインデックスを返す + return m; + } +} + +/* 二分探索 */ +int binarySearch(int nums[], int target, int numsSize) { + int n = numsSize; + // 問題 f(0, n-1) を解く + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +int main() { + int target = 6; + int nums[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + int numsSize = sizeof(nums) / sizeof(nums[0]); + + // 二分探索(両閉区間) + int index = binarySearch(nums, target, numsSize); + printf("対象要素 6 のインデックス = %d\n", index); + + return 0; +} diff --git a/ja/codes/c/chapter_divide_and_conquer/build_tree.c b/ja/codes/c/chapter_divide_and_conquer/build_tree.c new file mode 100644 index 000000000..06a7ef101 --- /dev/null +++ b/ja/codes/c/chapter_divide_and_conquer/build_tree.c @@ -0,0 +1,61 @@ +/** + * File : build_tree.c + * Created Time: 2023-10-16 + * Author : lucas (superrat6@gmail.com) + */ + +#include "../utils/common.h" + +// すべての要素が 1000 未満であると仮定する +#define MAX_SIZE 1000 + +/* 二分木を構築:分割統治 */ +TreeNode *dfs(int *preorder, int *inorderMap, int i, int l, int r, int size) { + // 部分木区間が空なら終了する + if (r - l < 0) + return NULL; + // ルートノードを初期化する + TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); + root->val = preorder[i]; + root->left = NULL; + root->right = NULL; + // m を求めて左右部分木を分割する + int m = inorderMap[preorder[i]]; + // 部分問題:左部分木を構築する + root->left = dfs(preorder, inorderMap, i + 1, l, m - 1, size); + // 部分問題:右部分木を構築する + root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r, size); + // 根ノードを返す + return root; +} + +/* 二分木を構築 */ +TreeNode *buildTree(int *preorder, int preorderSize, int *inorder, int inorderSize) { + // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する + int *inorderMap = (int *)malloc(sizeof(int) * MAX_SIZE); + for (int i = 0; i < inorderSize; i++) { + inorderMap[inorder[i]] = i; + } + TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorderSize - 1, inorderSize); + free(inorderMap); + return root; +} + +/* Driver Code */ +int main() { + int preorder[] = {3, 9, 2, 1, 7}; + int inorder[] = {9, 3, 1, 2, 7}; + int preorderSize = sizeof(preorder) / sizeof(preorder[0]); + int inorderSize = sizeof(inorder) / sizeof(inorder[0]); + printf("前順走査 = "); + printArray(preorder, preorderSize); + printf("中順走査 = "); + printArray(inorder, inorderSize); + + TreeNode *root = buildTree(preorder, preorderSize, inorder, inorderSize); + printf("構築した二分木は:\n"); + printTree(root); + + freeMemoryTree(root); + return 0; +} diff --git a/ja/codes/c/chapter_divide_and_conquer/hanota.c b/ja/codes/c/chapter_divide_and_conquer/hanota.c new file mode 100644 index 000000000..a3008e31c --- /dev/null +++ b/ja/codes/c/chapter_divide_and_conquer/hanota.c @@ -0,0 +1,74 @@ +/** + * File: hanota.c + * Created Time: 2023-10-01 + * Author: Zuoxun (845242523@qq.com), lucas(superrat6@gmail.com) + */ + +#include "../utils/common.h" + +// 順列は最大 1000 個と仮定 +#define MAX_SIZE 1000 + +/* 円盤を 1 枚移動 */ +void move(int *src, int *srcSize, int *tar, int *tarSize) { + // src の上から円盤を1枚取り出す + int pan = src[*srcSize - 1]; + src[*srcSize - 1] = 0; + (*srcSize)--; + // 円盤を tar の上に置く + tar[*tarSize] = pan; + (*tarSize)++; +} + +/* ハノイの塔の問題 f(i) を解く */ +void dfs(int i, int *src, int *srcSize, int *buf, int *bufSize, int *tar, int *tarSize) { + // src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す + if (i == 1) { + move(src, srcSize, tar, tarSize); + return; + } + // 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す + dfs(i - 1, src, srcSize, tar, tarSize, buf, bufSize); + // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す + move(src, srcSize, tar, tarSize); + // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す + dfs(i - 1, buf, bufSize, src, srcSize, tar, tarSize); +} + +/* ハノイの塔を解く */ +void solveHanota(int *A, int *ASize, int *B, int *BSize, int *C, int *CSize) { + // A の上から n 枚の円盤を B を介して C へ移す + dfs(*ASize, A, ASize, B, BSize, C, CSize); +} + +/* Driver Code */ +int main() { + // リスト末尾が柱の頂上 + int a[] = {5, 4, 3, 2, 1}; + int b[MAX_SIZE] = {0}; + int c[MAX_SIZE] = {0}; + + int ASize = sizeof(a) / sizeof(a[0]); + int BSize = 0; + int CSize = 0; + + printf("\n初期状態:"); + printf("\nA = "); + printArray(a, ASize); + printf("B = "); + printArray(b, BSize); + printf("C = "); + printArray(c, CSize); + + solveHanota(a, &ASize, b, &BSize, c, &CSize); + + printf("\n円盤の移動完了後:"); + printf("A = "); + printArray(a, ASize); + printf("B = "); + printArray(b, BSize); + printf("C = "); + printArray(c, CSize); + + return 0; +} diff --git a/ja/codes/c/chapter_dynamic_programming/CMakeLists.txt b/ja/codes/c/chapter_dynamic_programming/CMakeLists.txt new file mode 100644 index 000000000..dd769ebd5 --- /dev/null +++ b/ja/codes/c/chapter_dynamic_programming/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(climbing_stairs_constraint_dp climbing_stairs_constraint_dp.c) +add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.c) +add_executable(min_path_sum min_path_sum.c) +add_executable(knapsack knapsack.c) +add_executable(unbounded_knapsack unbounded_knapsack.c) +add_executable(coin_change coin_change.c) +add_executable(coin_change_ii coin_change_ii.c) +add_executable(edit_distance edit_distance.c) diff --git a/ja/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c b/ja/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c new file mode 100644 index 000000000..deb7af44a --- /dev/null +++ b/ja/codes/c/chapter_dynamic_programming/climbing_stairs_backtrack.c @@ -0,0 +1,47 @@ +/** + * File: climbing_stairs_backtrack.c + * Created Time: 2023-09-22 + * Author: huawuque404 (huawuque404@163.com) + */ + +#include "../utils/common.h" + +/* バックトラッキング */ +void backtrack(int *choices, int state, int n, int *res, int len) { + // 第 n 段に到達したら、方法数を 1 増やす + if (state == n) + res[0]++; + // すべての選択肢を走査 + for (int i = 0; i < len; i++) { + int choice = choices[i]; + // 枝刈り: 第 n 段を超えないようにする + if (state + choice > n) + continue; + // 試行: 選択を行い、状態を更新 + backtrack(choices, state + choice, n, res, len); + // バックトラック + } +} + +/* 階段登り:バックトラッキング */ +int climbingStairsBacktrack(int n) { + int choices[2] = {1, 2}; // 1 段または 2 段上ることを選べる + int state = 0; // 第 0 段から上り始める + int *res = (int *)malloc(sizeof(int)); + *res = 0; // res[0] を使って方法数を記録する + int len = sizeof(choices) / sizeof(int); + backtrack(choices, state, n, res, len); + int result = *res; + free(res); + return result; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsBacktrack(n); + printf("%d 段の階段を登る方法は全部で %d 通りです\n", n, res); + + return 0; +} \ No newline at end of file diff --git a/ja/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c b/ja/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c new file mode 100644 index 000000000..d5c9b18ec --- /dev/null +++ b/ja/codes/c/chapter_dynamic_programming/climbing_stairs_constraint_dp.c @@ -0,0 +1,46 @@ +/** + * File: climbing_stairs_constraint_dp.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 制約付き階段登り:動的計画法 */ +int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 部分問題の解を保存するために dp テーブルを初期化 + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(3, sizeof(int)); + } + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for (int i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + int res = dp[n][1] + dp[n][2]; + // メモリを解放する + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + free(dp); + return res; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsConstraintDP(n); + printf("%d 段の階段を登る方法は全部で %d 通りです\n", n, res); + + return 0; +} diff --git a/ja/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c b/ja/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c new file mode 100644 index 000000000..ee1ca8435 --- /dev/null +++ b/ja/codes/c/chapter_dynamic_programming/climbing_stairs_dfs.c @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs.c + * Created Time: 2023-09-19 + * Author: huawuque404 (huawuque404@163.com) + */ + +#include "../utils/common.h" + +/* 検索 */ +int dfs(int i) { + // dp[1] と dp[2] は既知なので返す + if (i == 1 || i == 2) + return i; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* 階段登り:探索 */ +int climbingStairsDFS(int n) { + return dfs(n); +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFS(n); + printf("%d 段の階段を登る方法は全部で %d 通りです\n", n, res); + + return 0; +} \ No newline at end of file diff --git a/ja/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c b/ja/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c new file mode 100644 index 000000000..3f776c283 --- /dev/null +++ b/ja/codes/c/chapter_dynamic_programming/climbing_stairs_dfs_mem.c @@ -0,0 +1,44 @@ +/** + * File: climbing_stairs_dfs_mem.c + * Created Time: 2023-09-19 + * Author: huawuque404 (huawuque404@163.com) + */ + +#include "../utils/common.h" + +/* メモ化探索 */ +int dfs(int i, int *mem) { + // dp[1] と dp[2] は既知なので返す + if (i == 1 || i == 2) + return i; + // dp[i] の記録があれば、それをそのまま返す + if (mem[i] != -1) + return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1, mem) + dfs(i - 2, mem); + // dp[i] を記録する + mem[i] = count; + return count; +} + +/* 階段登り:メモ化探索 */ +int climbingStairsDFSMem(int n) { + // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す + int *mem = (int *)malloc((n + 1) * sizeof(int)); + for (int i = 0; i <= n; i++) { + mem[i] = -1; + } + int result = dfs(n, mem); + free(mem); + return result; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDFSMem(n); + printf("%d 段の階段を登る方法は全部で %d 通りです\n", n, res); + + return 0; +} \ No newline at end of file diff --git a/ja/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c b/ja/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c new file mode 100644 index 000000000..ee9a336dd --- /dev/null +++ b/ja/codes/c/chapter_dynamic_programming/climbing_stairs_dp.c @@ -0,0 +1,51 @@ +/** + * File: climbing_stairs_dp.c + * Created Time: 2023-09-19 + * Author: huawuque404 (huawuque404@163.com) + */ + +#include "../utils/common.h" + +/* 階段登り:動的計画法 */ +int climbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // 部分問題の解を保存するために dp テーブルを初期化 + int *dp = (int *)malloc((n + 1) * sizeof(int)); + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1] = 1; + dp[2] = 2; + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + int result = dp[n]; + free(dp); + return result; +} + +/* 階段登り:空間最適化した動的計画法 */ +int climbingStairsDPComp(int n) { + if (n == 1 || n == 2) + return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + int n = 9; + + int res = climbingStairsDP(n); + printf("%d 段の階段を登る方法は全部で %d 通りです\n", n, res); + + res = climbingStairsDPComp(n); + printf("%d 段の階段を登る方法は全部で %d 通りです\n", n, res); + + return 0; +} \ No newline at end of file diff --git a/ja/codes/c/chapter_dynamic_programming/coin_change.c b/ja/codes/c/chapter_dynamic_programming/coin_change.c new file mode 100644 index 000000000..e7aaaf868 --- /dev/null +++ b/ja/codes/c/chapter_dynamic_programming/coin_change.c @@ -0,0 +1,92 @@ +/** + * File: coin_change.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 最小値を求める */ +int myMin(int a, int b) { + return a < b ? a : b; +} + +/* コイン両替:動的計画法 */ +int coinChangeDP(int coins[], int amt, int coinsSize) { + int n = coinsSize; + int MAX = amt + 1; + // dp テーブルを初期化 + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(amt + 1, sizeof(int)); + } + // 状態遷移:先頭行と先頭列 + for (int a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 状態遷移: 残りの行と列 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[i][a] = dp[i - 1][a]; + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[i][a] = myMin(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + int res = dp[n][amt] != MAX ? dp[n][amt] : -1; + // メモリを解放する + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + free(dp); + return res; +} + +/* コイン交換:空間最適化後の動的計画法 */ +int coinChangeDPComp(int coins[], int amt, int coinsSize) { + int n = coinsSize; + int MAX = amt + 1; + // dp テーブルを初期化 + int *dp = malloc((amt + 1) * sizeof(int)); + for (int j = 1; j <= amt; j++) { + dp[j] = MAX; + } + dp[0] = 0; + + // 状態遷移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a]; + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[a] = myMin(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + int res = dp[amt] != MAX ? dp[amt] : -1; + // メモリを解放する + free(dp); + return res; +} + +/* Driver code */ +int main() { + int coins[] = {1, 2, 5}; + int coinsSize = sizeof(coins) / sizeof(coins[0]); + int amt = 4; + + // 動的計画法 + int res = coinChangeDP(coins, amt, coinsSize); + printf("目標金額に必要な最小硬貨枚数は %d\n", res); + + // 空間最適化後の動的計画法 + res = coinChangeDPComp(coins, amt, coinsSize); + printf("目標金額に必要な最小硬貨枚数は %d\n", res); + + return 0; +} diff --git a/ja/codes/c/chapter_dynamic_programming/coin_change_ii.c b/ja/codes/c/chapter_dynamic_programming/coin_change_ii.c new file mode 100644 index 000000000..f318fa422 --- /dev/null +++ b/ja/codes/c/chapter_dynamic_programming/coin_change_ii.c @@ -0,0 +1,81 @@ +/** + * File: coin_change_ii.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* コイン両替 II:動的計画法 */ +int coinChangeIIDP(int coins[], int amt, int coinsSize) { + int n = coinsSize; + // dp テーブルを初期化 + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(amt + 1, sizeof(int)); + } + // 先頭列を初期化する + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 状態遷移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[i][a] = dp[i - 1][a]; + } else { + // コイン i を選ばない場合と選ぶ場合の和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + int res = dp[n][amt]; + // メモリを解放する + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + free(dp); + return res; +} + +/* コイン両替 II:空間最適化した動的計画法 */ +int coinChangeIIDPComp(int coins[], int amt, int coinsSize) { + int n = coinsSize; + // dp テーブルを初期化 + int *dp = calloc(amt + 1, sizeof(int)); + dp[0] = 1; + // 状態遷移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a]; + } else { + // コイン i を選ばない場合と選ぶ場合の和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + int res = dp[amt]; + // メモリを解放する + free(dp); + return res; +} + +/* Driver code */ +int main() { + int coins[] = {1, 2, 5}; + int coinsSize = sizeof(coins) / sizeof(coins[0]); + int amt = 5; + + // 動的計画法 + int res = coinChangeIIDP(coins, amt, coinsSize); + printf("目標金額になる硬貨の組合せ数は %d\n", res); + + // 空間最適化後の動的計画法 + res = coinChangeIIDPComp(coins, amt, coinsSize); + printf("目標金額になる硬貨の組合せ数は %d\n", res); + + return 0; +} diff --git a/ja/codes/c/chapter_dynamic_programming/edit_distance.c b/ja/codes/c/chapter_dynamic_programming/edit_distance.c new file mode 100644 index 000000000..d2e9fe329 --- /dev/null +++ b/ja/codes/c/chapter_dynamic_programming/edit_distance.c @@ -0,0 +1,159 @@ +/** + * File: edit_distance.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 最小値を求める */ +int myMin(int a, int b) { + return a < b ? a : b; +} + +/* 編集距離:総当たり探索 */ +int editDistanceDFS(char *s, char *t, int i, int j) { + // s と t がともに空なら 0 を返す + if (i == 0 && j == 0) + return 0; + // s が空なら t の長さを返す + if (i == 0) + return j; + // t が空なら s の長さを返す + if (j == 0) + return i; + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if (s[i - 1] == t[j - 1]) + return editDistanceDFS(s, t, i - 1, j - 1); + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + int insert = editDistanceDFS(s, t, i, j - 1); + int del = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // 最小編集回数を返す + return myMin(myMin(insert, del), replace) + 1; +} + +/* 編集距離:メモ化探索 */ +int editDistanceDFSMem(char *s, char *t, int memCols, int **mem, int i, int j) { + // s と t がともに空なら 0 を返す + if (i == 0 && j == 0) + return 0; + // s が空なら t の長さを返す + if (i == 0) + return j; + // t が空なら s の長さを返す + if (j == 0) + return i; + // 記録済みなら、それをそのまま返す + if (mem[i][j] != -1) + return mem[i][j]; + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if (s[i - 1] == t[j - 1]) + return editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1); + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + int insert = editDistanceDFSMem(s, t, memCols, mem, i, j - 1); + int del = editDistanceDFSMem(s, t, memCols, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, memCols, mem, i - 1, j - 1); + // 最小編集回数を記録して返す + mem[i][j] = myMin(myMin(insert, del), replace) + 1; + return mem[i][j]; +} + +/* 編集距離:動的計画法 */ +int editDistanceDP(char *s, char *t, int n, int m) { + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(m + 1, sizeof(int)); + } + // 状態遷移:先頭行と先頭列 + for (int i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 状態遷移: 残りの行と列 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s[i - 1] == t[j - 1]) { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[i][j] = myMin(myMin(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + int res = dp[n][m]; + // メモリを解放する + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + return res; +} + +/* 編集距離:空間最適化した動的計画法 */ +int editDistanceDPComp(char *s, char *t, int n, int m) { + int *dp = calloc(m + 1, sizeof(int)); + // 状態遷移:先頭行 + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // 状態遷移:残りの行 + for (int i = 1; i <= n; i++) { + // 状態遷移:先頭列 + int leftup = dp[0]; // dp[i-1, j-1] を一時保存する + dp[0] = i; + // 状態遷移:残りの列 + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[j] = leftup; + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[j] = myMin(myMin(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 次の反復の dp[i-1, j-1] に更新する + } + } + int res = dp[m]; + // メモリを解放する + free(dp); + return res; +} + +/* Driver Code */ +int main() { + char *s = "bag"; + char *t = "pack"; + int n = strlen(s), m = strlen(t); + + // 全探索 + int res = editDistanceDFS(s, t, n, m); + printf("%s を %s に変更するには最小で %d 回の編集が必要です\n", s, t, res); + + // メモ化探索 + int **mem = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + mem[i] = malloc((m + 1) * sizeof(int)); + memset(mem[i], -1, (m + 1) * sizeof(int)); + } + res = editDistanceDFSMem(s, t, m + 1, mem, n, m); + printf("%s を %s に変更するには最小で %d 回の編集が必要です\n", s, t, res); + // メモリを解放する + for (int i = 0; i <= n; i++) { + free(mem[i]); + } + free(mem); + + // 動的計画法 + res = editDistanceDP(s, t, n, m); + printf("%s を %s に変更するには最小で %d 回の編集が必要です\n", s, t, res); + + // 空間最適化後の動的計画法 + res = editDistanceDPComp(s, t, n, m); + printf("%s を %s に変更するには最小で %d 回の編集が必要です\n", s, t, res); + + return 0; +} diff --git a/ja/codes/c/chapter_dynamic_programming/knapsack.c b/ja/codes/c/chapter_dynamic_programming/knapsack.c new file mode 100644 index 000000000..669e64a01 --- /dev/null +++ b/ja/codes/c/chapter_dynamic_programming/knapsack.c @@ -0,0 +1,137 @@ +/** + * File: knapsack.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 最大値を求める */ +int myMax(int a, int b) { + return a > b ? a : b; +} + +/* 0-1 ナップサック:総当たり探索 */ +int knapsackDFS(int wgt[], int val[], int i, int c) { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if (i == 0 || c == 0) { + return 0; + } + // ナップサック容量を超える場合は、入れない選択しかできない + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + int no = knapsackDFS(wgt, val, i - 1, c); + int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 2つの案のうち価値が大きいほうを返す + return myMax(no, yes); +} + +/* 0-1 ナップサック:メモ化探索 */ +int knapsackDFSMem(int wgt[], int val[], int memCols, int **mem, int i, int c) { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if (i == 0 || c == 0) { + return 0; + } + // 既に記録があればそのまま返す + if (mem[i][c] != -1) { + return mem[i][c]; + } + // ナップサック容量を超える場合は、入れない選択しかできない + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, memCols, mem, i - 1, c); + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + int no = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, memCols, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 2 つの案のうち価値が大きい方を記録して返す + mem[i][c] = myMax(no, yes); + return mem[i][c]; +} + +/* 0-1 ナップサック:動的計画法 */ +int knapsackDP(int wgt[], int val[], int cap, int wgtSize) { + int n = wgtSize; + // dp テーブルを初期化 + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(cap + 1, sizeof(int)); + } + // 状態遷移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[i][c] = dp[i - 1][c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i][c] = myMax(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + int res = dp[n][cap]; + // メモリを解放する + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + return res; +} + +/* 0-1 ナップサック:空間最適化後の動的計画法 */ +int knapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { + int n = wgtSize; + // dp テーブルを初期化 + int *dp = calloc(cap + 1, sizeof(int)); + // 状態遷移 + for (int i = 1; i <= n; i++) { + // 逆順に走査する + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + int res = dp[cap]; + // メモリを解放する + free(dp); + return res; +} + +/* Driver Code */ +int main() { + int wgt[] = {10, 20, 30, 40, 50}; + int val[] = {50, 120, 150, 210, 240}; + int cap = 50; + int n = sizeof(wgt) / sizeof(wgt[0]); + int wgtSize = n; + + // 全探索 + int res = knapsackDFS(wgt, val, n, cap); + printf("ナップサック容量を超えない最大価値は %d\n", res); + + // メモ化探索 + int **mem = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + mem[i] = malloc((cap + 1) * sizeof(int)); + memset(mem[i], -1, (cap + 1) * sizeof(int)); + } + res = knapsackDFSMem(wgt, val, cap + 1, mem, n, cap); + printf("ナップサック容量を超えない最大価値は %d\n", res); + // メモリを解放する + for (int i = 0; i <= n; i++) { + free(mem[i]); + } + free(mem); + + // 動的計画法 + res = knapsackDP(wgt, val, cap, wgtSize); + printf("ナップサック容量を超えない最大価値は %d\n", res); + + // 空間最適化後の動的計画法 + res = knapsackDPComp(wgt, val, cap, wgtSize); + printf("ナップサック容量を超えない最大価値は %d\n", res); + + return 0; +} diff --git a/ja/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c b/ja/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c new file mode 100644 index 000000000..c00105f18 --- /dev/null +++ b/ja/codes/c/chapter_dynamic_programming/min_cost_climbing_stairs_dp.c @@ -0,0 +1,62 @@ +/** + * File: min_cost_climbing_stairs_dp.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 最小値を求める */ +int myMin(int a, int b) { + return a < b ? a : b; +} + +/* 階段登りの最小コスト:動的計画法 */ +int minCostClimbingStairsDP(int cost[], int costSize) { + int n = costSize - 1; + if (n == 1 || n == 2) + return cost[n]; + // 部分問題の解を保存するために dp テーブルを初期化 + int *dp = calloc(n + 1, sizeof(int)); + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for (int i = 3; i <= n; i++) { + dp[i] = myMin(dp[i - 1], dp[i - 2]) + cost[i]; + } + int res = dp[n]; + // メモリを解放する + free(dp); + return res; +} + +/* 階段昇りの最小コスト:空間最適化後の動的計画法 */ +int minCostClimbingStairsDPComp(int cost[], int costSize) { + int n = costSize - 1; + if (n == 1 || n == 2) + return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = myMin(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +int main() { + int cost[] = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; + int costSize = sizeof(cost) / sizeof(cost[0]); + printf("入力された階段コストのリストは:"); + printArray(cost, costSize); + + int res = minCostClimbingStairsDP(cost, costSize); + printf("階段を登り切る最小コストは %d\n", res); + + res = minCostClimbingStairsDPComp(cost, costSize); + printf("階段を登り切る最小コストは %d\n", res); + + return 0; +} diff --git a/ja/codes/c/chapter_dynamic_programming/min_path_sum.c b/ja/codes/c/chapter_dynamic_programming/min_path_sum.c new file mode 100644 index 000000000..e5a5ce5f9 --- /dev/null +++ b/ja/codes/c/chapter_dynamic_programming/min_path_sum.c @@ -0,0 +1,134 @@ +/** + * File: min_path_sum.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +// 行列の最大行数・列数を 100 と仮定する +#define MAX_SIZE 100 + +/* 最小値を求める */ +int myMin(int a, int b) { + return a < b ? a : b; +} + +/* 最小経路和:全探索 */ +int minPathSumDFS(int grid[MAX_SIZE][MAX_SIZE], int i, int j) { + // 左上のセルなら探索を終了する + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if (i < 0 || j < 0) { + return INT_MAX; + } + // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // 左上隅から (i, j) までの最小経路コストを返す + return myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX; +} + +/* 最小経路和:メモ化探索 */ +int minPathSumDFSMem(int grid[MAX_SIZE][MAX_SIZE], int mem[MAX_SIZE][MAX_SIZE], int i, int j) { + // 左上のセルなら探索を終了する + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if (i < 0 || j < 0) { + return INT_MAX; + } + // 既に記録があればそのまま返す + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左と上のセルからの最小経路コスト + int up = minPathSumDFSMem(grid, mem, i - 1, j); + int left = minPathSumDFSMem(grid, mem, i, j - 1); + // 左上から (i, j) までの最小経路コストを記録して返す + mem[i][j] = myMin(left, up) != INT_MAX ? myMin(left, up) + grid[i][j] : INT_MAX; + return mem[i][j]; +} + +/* 最小経路和:動的計画法 */ +int minPathSumDP(int grid[MAX_SIZE][MAX_SIZE], int n, int m) { + // dp テーブルを初期化 + int **dp = malloc(n * sizeof(int *)); + for (int i = 0; i < n; i++) { + dp[i] = calloc(m, sizeof(int)); + } + dp[0][0] = grid[0][0]; + // 状態遷移:先頭行 + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 状態遷移:先頭列 + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 状態遷移: 残りの行と列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = myMin(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + int res = dp[n - 1][m - 1]; + // メモリを解放する + for (int i = 0; i < n; i++) { + free(dp[i]); + } + return res; +} + +/* 最小経路和:空間最適化後の動的計画法 */ +int minPathSumDPComp(int grid[MAX_SIZE][MAX_SIZE], int n, int m) { + // dp テーブルを初期化 + int *dp = calloc(m, sizeof(int)); + // 状態遷移:先頭行 + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状態遷移:残りの行 + for (int i = 1; i < n; i++) { + // 状態遷移:先頭列 + dp[0] = dp[0] + grid[i][0]; + // 状態遷移:残りの列 + for (int j = 1; j < m; j++) { + dp[j] = myMin(dp[j - 1], dp[j]) + grid[i][j]; + } + } + int res = dp[m - 1]; + // メモリを解放する + free(dp); + return res; +} + +/* Driver Code */ +int main() { + int grid[MAX_SIZE][MAX_SIZE] = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; + int n = 4, m = 4; // 行列の容量は `MAX_SIZE * MAX_SIZE`、有効な行数と列数は `n * m` + + // 全探索 + int res = minPathSumDFS(grid, n - 1, m - 1); + printf("左上から右下までの最小経路和は %d\n", res); + + // メモ化探索 + int mem[MAX_SIZE][MAX_SIZE]; + memset(mem, -1, sizeof(mem)); + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + printf("左上から右下までの最小経路和は %d\n", res); + + // 動的計画法 + res = minPathSumDP(grid, n, m); + printf("左上から右下までの最小経路和は %d\n", res); + + // 空間最適化後の動的計画法 + res = minPathSumDPComp(grid, n, m); + printf("左上から右下までの最小経路和は %d\n", res); + + return 0; +} diff --git a/ja/codes/c/chapter_dynamic_programming/unbounded_knapsack.c b/ja/codes/c/chapter_dynamic_programming/unbounded_knapsack.c new file mode 100644 index 000000000..2403cb9b7 --- /dev/null +++ b/ja/codes/c/chapter_dynamic_programming/unbounded_knapsack.c @@ -0,0 +1,81 @@ +/** + * File: unbounded_knapsack.c + * Created Time: 2023-10-02 + * Author: Zuoxun (845242523@qq.com) + */ + +#include "../utils/common.h" + +/* 最大値を求める */ +int myMax(int a, int b) { + return a > b ? a : b; +} + +/* 完全ナップサック問題:動的計画法 */ +int unboundedKnapsackDP(int wgt[], int val[], int cap, int wgtSize) { + int n = wgtSize; + // dp テーブルを初期化 + int **dp = malloc((n + 1) * sizeof(int *)); + for (int i = 0; i <= n; i++) { + dp[i] = calloc(cap + 1, sizeof(int)); + } + // 状態遷移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[i][c] = dp[i - 1][c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i][c] = myMax(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); + } + } + } + int res = dp[n][cap]; + // メモリを解放する + for (int i = 0; i <= n; i++) { + free(dp[i]); + } + return res; +} + +/* 完全ナップサック問題:空間最適化後の動的計画法 */ +int unboundedKnapsackDPComp(int wgt[], int val[], int cap, int wgtSize) { + int n = wgtSize; + // dp テーブルを初期化 + int *dp = calloc(cap + 1, sizeof(int)); + // 状態遷移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[c] = dp[c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = myMax(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + int res = dp[cap]; + // メモリを解放する + free(dp); + return res; +} + +/* Driver code */ +int main() { + int wgt[] = {1, 2, 3}; + int val[] = {5, 11, 15}; + int wgtSize = sizeof(wgt) / sizeof(wgt[0]); + int cap = 4; + + // 動的計画法 + int res = unboundedKnapsackDP(wgt, val, cap, wgtSize); + printf("ナップサック容量を超えない最大価値は %d\n", res); + + // 空間最適化後の動的計画法 + res = unboundedKnapsackDPComp(wgt, val, cap, wgtSize); + printf("ナップサック容量を超えない最大価値は %d\n", res); + + return 0; +} diff --git a/ja/codes/c/chapter_graph/CMakeLists.txt b/ja/codes/c/chapter_graph/CMakeLists.txt new file mode 100644 index 000000000..28f8470f4 --- /dev/null +++ b/ja/codes/c/chapter_graph/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(graph_adjacency_matrix graph_adjacency_matrix.c) +add_executable(graph_adjacency_list_test graph_adjacency_list_test.c) +add_executable(graph_bfs graph_bfs.c) +add_executable(graph_dfs graph_dfs.c) diff --git a/ja/codes/c/chapter_graph/graph_adjacency_list.c b/ja/codes/c/chapter_graph/graph_adjacency_list.c new file mode 100644 index 000000000..cbbd31b9e --- /dev/null +++ b/ja/codes/c/chapter_graph/graph_adjacency_list.c @@ -0,0 +1,171 @@ +/** + * File: graph_adjacency_list.c + * Created Time: 2023-07-07 + * Author: NI-SW (947743645@qq.com) + */ + +#include "../utils/common.h" + +// ノード数の上限を 100 と仮定 +#define MAX_SIZE 100 + +/* ノード構造体 */ +typedef struct AdjListNode { + Vertex *vertex; // 頂点 + struct AdjListNode *next; // 後続ノード +} AdjListNode; + +/* 隣接リストに基づく無向グラフクラス */ +typedef struct { + AdjListNode *heads[MAX_SIZE]; // ノード配列 + int size; // ノード数 +} GraphAdjList; + +/* コンストラクタ */ +GraphAdjList *newGraphAdjList() { + GraphAdjList *graph = (GraphAdjList *)malloc(sizeof(GraphAdjList)); + if (!graph) { + return NULL; + } + graph->size = 0; + for (int i = 0; i < MAX_SIZE; i++) { + graph->heads[i] = NULL; + } + return graph; +} + +/* デストラクタ */ +void delGraphAdjList(GraphAdjList *graph) { + for (int i = 0; i < graph->size; i++) { + AdjListNode *cur = graph->heads[i]; + while (cur != NULL) { + AdjListNode *next = cur->next; + if (cur != graph->heads[i]) { + free(cur); + } + cur = next; + } + free(graph->heads[i]->vertex); + free(graph->heads[i]); + } + free(graph); +} + +/* 頂点に対応するノードを検索 */ +AdjListNode *findNode(GraphAdjList *graph, Vertex *vet) { + for (int i = 0; i < graph->size; i++) { + if (graph->heads[i]->vertex == vet) { + return graph->heads[i]; + } + } + return NULL; +} + +/* 辺を追加する補助関数 */ +void addEdgeHelper(AdjListNode *head, Vertex *vet) { + AdjListNode *node = (AdjListNode *)malloc(sizeof(AdjListNode)); + node->vertex = vet; + // 先頭挿入法 + node->next = head->next; + head->next = node; +} + +/* 辺を追加 */ +void addEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { + AdjListNode *head1 = findNode(graph, vet1); + AdjListNode *head2 = findNode(graph, vet2); + assert(head1 != NULL && head2 != NULL && head1 != head2); + // 辺 vet1 - vet2 を追加 + addEdgeHelper(head1, vet2); + addEdgeHelper(head2, vet1); +} + +/* 辺削除の補助関数 */ +void removeEdgeHelper(AdjListNode *head, Vertex *vet) { + AdjListNode *pre = head; + AdjListNode *cur = head->next; + // 連結リスト内で vet に対応するノードを探索 + while (cur != NULL && cur->vertex != vet) { + pre = cur; + cur = cur->next; + } + if (cur == NULL) + return; + // vet に対応するノードを連結リストから削除 + pre->next = cur->next; + // メモリを解放する + free(cur); +} + +/* 辺を削除 */ +void removeEdge(GraphAdjList *graph, Vertex *vet1, Vertex *vet2) { + AdjListNode *head1 = findNode(graph, vet1); + AdjListNode *head2 = findNode(graph, vet2); + assert(head1 != NULL && head2 != NULL); + // 辺 vet1 - vet2 を削除 + removeEdgeHelper(head1, head2->vertex); + removeEdgeHelper(head2, head1->vertex); +} + +/* 頂点を追加 */ +void addVertex(GraphAdjList *graph, Vertex *vet) { + assert(graph != NULL && graph->size < MAX_SIZE); + AdjListNode *head = (AdjListNode *)malloc(sizeof(AdjListNode)); + head->vertex = vet; + head->next = NULL; + // 隣接リストに新しいリストを追加 + graph->heads[graph->size++] = head; +} + +/* 頂点を削除 */ +void removeVertex(GraphAdjList *graph, Vertex *vet) { + AdjListNode *node = findNode(graph, vet); + assert(node != NULL); + // 隣接リストから頂点 vet に対応するリストを削除 + AdjListNode *cur = node, *pre = NULL; + while (cur) { + pre = cur; + cur = cur->next; + free(pre); + } + // 他の頂点のリストを走査し、vet を含むすべての辺を削除 + for (int i = 0; i < graph->size; i++) { + cur = graph->heads[i]; + pre = NULL; + while (cur) { + pre = cur; + cur = cur->next; + if (cur && cur->vertex == vet) { + pre->next = cur->next; + free(cur); + break; + } + } + } + // この頂点より後ろの頂点を前に詰めて欠損を埋める + int i; + for (i = 0; i < graph->size; i++) { + if (graph->heads[i] == node) + break; + } + for (int j = i; j < graph->size - 1; j++) { + graph->heads[j] = graph->heads[j + 1]; + } + graph->size--; + free(vet); +} + +/* 隣接リストを出力 */ +void printGraph(const GraphAdjList *graph) { + printf("隣接リスト =\n"); + for (int i = 0; i < graph->size; ++i) { + AdjListNode *node = graph->heads[i]; + printf("%d: [", node->vertex->val); + node = node->next; + while (node) { + printf("%d, ", node->vertex->val); + node = node->next; + } + printf("]\n"); + } +} diff --git a/ja/codes/c/chapter_graph/graph_adjacency_list_test.c b/ja/codes/c/chapter_graph/graph_adjacency_list_test.c new file mode 100644 index 000000000..d1b06a4a7 --- /dev/null +++ b/ja/codes/c/chapter_graph/graph_adjacency_list_test.c @@ -0,0 +1,55 @@ +/** + * File: graph_adjacency_list_test.c + * Created Time: 2023-07-11 + * Author: NI-SW (947743645@qq.com) + */ + +#include "graph_adjacency_list.c" + +/* Driver Code */ +int main() { + int vals[] = {1, 3, 2, 5, 4}; + int size = sizeof(vals) / sizeof(vals[0]); + Vertex **v = valsToVets(vals, size); + Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; + int egdeSize = sizeof(edges) / sizeof(edges[0]); + GraphAdjList *graph = newGraphAdjList(); + // すべての頂点と辺を追加 + for (int i = 0; i < size; i++) { + addVertex(graph, v[i]); + } + for (int i = 0; i < egdeSize; i++) { + addEdge(graph, edges[i][0], edges[i][1]); + } + printf("\n初期化後のグラフは\n"); + printGraph(graph); + + /* 辺を追加 */ + // 頂点 1, 2 は v[0], v[2] + addEdge(graph, v[0], v[2]); + printf("\n辺 1-2 を追加した後のグラフは\n"); + printGraph(graph); + + /* 辺を削除 */ + // 頂点 1, 3 は v[0], v[1] + removeEdge(graph, v[0], v[1]); + printf("\n辺 1-3 を削除した後のグラフは\n"); + printGraph(graph); + + /* 頂点を追加 */ + Vertex *v5 = newVertex(6); + addVertex(graph, v5); + printf("\n頂点 6 を追加した後のグラフは\n"); + printGraph(graph); + + /* 頂点を削除 */ + // 頂点 3 は v[1] + removeVertex(graph, v[1]); + printf("\n頂点 3 を削除すると、グラフは次のようになります:\n"); + printGraph(graph); + + // メモリを解放する + delGraphAdjList(graph); + free(v); + return 0; +} diff --git a/ja/codes/c/chapter_graph/graph_adjacency_matrix.c b/ja/codes/c/chapter_graph/graph_adjacency_matrix.c new file mode 100644 index 000000000..8e9b06da8 --- /dev/null +++ b/ja/codes/c/chapter_graph/graph_adjacency_matrix.c @@ -0,0 +1,150 @@ +/** + * File: graph_adjacency_matrix.c + * Created Time: 2023-07-06 + * Author: NI-SW (947743645@qq.com) + */ + +#include "../utils/common.h" + +// 頂点数の最大値を 100 と仮定する +#define MAX_SIZE 100 + +/* 隣接行列に基づく無向グラフ構造体 */ +typedef struct { + int vertices[MAX_SIZE]; + int adjMat[MAX_SIZE][MAX_SIZE]; + int size; +} GraphAdjMat; + +/* コンストラクタ */ +GraphAdjMat *newGraphAdjMat() { + GraphAdjMat *graph = (GraphAdjMat *)malloc(sizeof(GraphAdjMat)); + graph->size = 0; + for (int i = 0; i < MAX_SIZE; i++) { + for (int j = 0; j < MAX_SIZE; j++) { + graph->adjMat[i][j] = 0; + } + } + return graph; +} + +/* デストラクタ */ +void delGraphAdjMat(GraphAdjMat *graph) { + free(graph); +} + +/* 頂点を追加 */ +void addVertex(GraphAdjMat *graph, int val) { + if (graph->size == MAX_SIZE) { + fprintf(stderr, "グラフの頂点数が最大値に達しました\n"); + return; + } + // n 番目の頂点を追加し、n 行目と n 列目を 0 にする + int n = graph->size; + graph->vertices[n] = val; + for (int i = 0; i <= n; i++) { + graph->adjMat[n][i] = graph->adjMat[i][n] = 0; + } + graph->size++; +} + +/* 頂点を削除 */ +void removeVertex(GraphAdjMat *graph, int index) { + if (index < 0 || index >= graph->size) { + fprintf(stderr, "頂点インデックスが範囲外です\n"); + return; + } + // 頂点リストから index の頂点を削除する + for (int i = index; i < graph->size - 1; i++) { + graph->vertices[i] = graph->vertices[i + 1]; + } + // 隣接行列で index 行を削除する + for (int i = index; i < graph->size - 1; i++) { + for (int j = 0; j < graph->size; j++) { + graph->adjMat[i][j] = graph->adjMat[i + 1][j]; + } + } + // 隣接行列で index 列を削除する + for (int i = 0; i < graph->size; i++) { + for (int j = index; j < graph->size - 1; j++) { + graph->adjMat[i][j] = graph->adjMat[i][j + 1]; + } + } + graph->size--; +} + +/* 辺を追加 */ +// 引数 i, j は vertices の要素インデックスに対応する +void addEdge(GraphAdjMat *graph, int i, int j) { + if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) { + fprintf(stderr, "辺インデックスが範囲外であるか、同一です\n"); + return; + } + graph->adjMat[i][j] = 1; + graph->adjMat[j][i] = 1; +} + +/* 辺を削除 */ +// 引数 i, j は vertices の要素インデックスに対応する +void removeEdge(GraphAdjMat *graph, int i, int j) { + if (i < 0 || j < 0 || i >= graph->size || j >= graph->size || i == j) { + fprintf(stderr, "辺インデックスが範囲外であるか、同一です\n"); + return; + } + graph->adjMat[i][j] = 0; + graph->adjMat[j][i] = 0; +} + +/* 隣接行列を出力 */ +void printGraphAdjMat(GraphAdjMat *graph) { + printf("頂点リスト = "); + printArray(graph->vertices, graph->size); + printf("隣接行列 =\n"); + for (int i = 0; i < graph->size; i++) { + printArray(graph->adjMat[i], graph->size); + } +} + +/* Driver Code */ +int main() { + // 無向グラフを初期化 + GraphAdjMat *graph = newGraphAdjMat(); + int vertices[] = {1, 3, 2, 5, 4}; + for (int i = 0; i < 5; i++) { + addVertex(graph, vertices[i]); + } + int edges[][2] = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; + for (int i = 0; i < 6; i++) { + addEdge(graph, edges[i][0], edges[i][1]); + } + printf("\n初期化後のグラフは\n"); + printGraphAdjMat(graph); + + /* 辺を追加 */ + // 頂点 1, 2 のインデックスはそれぞれ 0, 2 + addEdge(graph, 0, 2); + printf("\n辺 1-2 を追加した後のグラフは\n"); + printGraphAdjMat(graph); + + /* 辺を削除 */ + // 頂点 1, 3 のインデックスはそれぞれ 0, 1 + removeEdge(graph, 0, 1); + printf("\n辺 1-3 を削除した後のグラフは\n"); + printGraphAdjMat(graph); + + /* 頂点を追加 */ + addVertex(graph, 6); + printf("\n頂点 6 を追加した後のグラフは\n"); + printGraphAdjMat(graph); + + /* 頂点を削除 */ + // 頂点 3 のインデックスは 1 + removeVertex(graph, 1); + printf("\n頂点 3 を削除すると、グラフは次のようになります\n"); + printGraphAdjMat(graph); + + // メモリを解放する + delGraphAdjMat(graph); + + return 0; +} diff --git a/ja/codes/c/chapter_graph/graph_bfs.c b/ja/codes/c/chapter_graph/graph_bfs.c new file mode 100644 index 000000000..822398b74 --- /dev/null +++ b/ja/codes/c/chapter_graph/graph_bfs.c @@ -0,0 +1,116 @@ +/** + * File: graph_bfs.c + * Created Time: 2023-07-11 + * Author: NI-SW (947743645@qq.com) + */ + +#include "graph_adjacency_list.c" + +// ノード数の上限を 100 と仮定 +#define MAX_SIZE 100 + +/* ノードキュー構造体 */ +typedef struct { + Vertex *vertices[MAX_SIZE]; + int front, rear, size; +} Queue; + +/* コンストラクタ */ +Queue *newQueue() { + Queue *q = (Queue *)malloc(sizeof(Queue)); + q->front = q->rear = q->size = 0; + return q; +} + +/* キューが空かどうかを判定 */ +int isEmpty(Queue *q) { + return q->size == 0; +} + +/* エンキュー操作 */ +void enqueue(Queue *q, Vertex *vet) { + q->vertices[q->rear] = vet; + q->rear = (q->rear + 1) % MAX_SIZE; + q->size++; +} + +/* デキュー操作 */ +Vertex *dequeue(Queue *q) { + Vertex *vet = q->vertices[q->front]; + q->front = (q->front + 1) % MAX_SIZE; + q->size--; + return vet; +} + +/* 頂点が訪問済みかを確認 */ +int isVisited(Vertex **visited, int size, Vertex *vet) { + // 走査してノードを探すため、O(n) 時間を要する + for (int i = 0; i < size; i++) { + if (visited[i] == vet) + return 1; + } + return 0; +} + +/* 幅優先探索 */ +// グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする +void graphBFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize, Vertex **visited, int *visitedSize) { + // BFS の実装にキューを用いる + Queue *queue = newQueue(); + enqueue(queue, startVet); + visited[(*visitedSize)++] = startVet; + // 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す + while (!isEmpty(queue)) { + Vertex *vet = dequeue(queue); // 先頭の頂点をデキュー + res[(*resSize)++] = vet; // 訪問した頂点を記録 + // この頂点のすべての隣接頂点を走査 + AdjListNode *node = findNode(graph, vet); + while (node != NULL) { + // 訪問済みの頂点をスキップ + if (!isVisited(visited, *visitedSize, node->vertex)) { + enqueue(queue, node->vertex); // 未訪問の頂点のみをキューに追加 + visited[(*visitedSize)++] = node->vertex; // この頂点を訪問済みにする + } + node = node->next; + } + } + // メモリを解放する + free(queue); +} + +/* Driver Code */ +int main() { + // 無向グラフを初期化 + int vals[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + int size = sizeof(vals) / sizeof(vals[0]); + Vertex **v = valsToVets(vals, size); + Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[1], v[4]}, {v[2], v[5]}, {v[3], v[4]}, + {v[3], v[6]}, {v[4], v[5]}, {v[4], v[7]}, {v[5], v[8]}, {v[6], v[7]}, {v[7], v[8]}}; + int egdeSize = sizeof(edges) / sizeof(edges[0]); + GraphAdjList *graph = newGraphAdjList(); + // すべての頂点と辺を追加 + for (int i = 0; i < size; i++) { + addVertex(graph, v[i]); + } + for (int i = 0; i < egdeSize; i++) { + addEdge(graph, edges[i][0], edges[i][1]); + } + printf("\n初期化後のグラフは\n"); + printGraph(graph); + + // 幅優先探索 + // 頂点の走査順序 + Vertex *res[MAX_SIZE]; + int resSize = 0; + // 訪問済みの頂点を記録する + Vertex *visited[MAX_SIZE]; + int visitedSize = 0; + graphBFS(graph, v[0], res, &resSize, visited, &visitedSize); + printf("\n幅優先探索(BFS)の頂点列は次のとおりです\n"); + printArray(vetsToVals(res, resSize), resSize); + + // メモリを解放する + delGraphAdjList(graph); + free(v); + return 0; +} diff --git a/ja/codes/c/chapter_graph/graph_dfs.c b/ja/codes/c/chapter_graph/graph_dfs.c new file mode 100644 index 000000000..c39452a6d --- /dev/null +++ b/ja/codes/c/chapter_graph/graph_dfs.c @@ -0,0 +1,75 @@ +/** + * File: graph_dfs.c + * Created Time: 2023-07-13 + * Author: NI-SW (947743645@qq.com) + */ + +#include "graph_adjacency_list.c" + +// ノード数の上限を 100 と仮定 +#define MAX_SIZE 100 + +/* 頂点が訪問済みかを確認 */ +int isVisited(Vertex **res, int size, Vertex *vet) { + // 走査してノードを探すため、O(n) 時間を要する + for (int i = 0; i < size; i++) { + if (res[i] == vet) { + return 1; + } + } + return 0; +} + +/* 深さ優先走査の補助関数 */ +void dfs(GraphAdjList *graph, Vertex **res, int *resSize, Vertex *vet) { + // 訪問した頂点を記録 + res[(*resSize)++] = vet; + // この頂点のすべての隣接頂点を走査 + AdjListNode *node = findNode(graph, vet); + while (node != NULL) { + // 訪問済みの頂点をスキップ + if (!isVisited(res, *resSize, node->vertex)) { + // 隣接頂点を再帰的に訪問 + dfs(graph, res, resSize, node->vertex); + } + node = node->next; + } +} + +/* 深さ優先探索 */ +// グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする +void graphDFS(GraphAdjList *graph, Vertex *startVet, Vertex **res, int *resSize) { + dfs(graph, res, resSize, startVet); +} + +/* Driver Code */ +int main() { + // 無向グラフを初期化 + int vals[] = {0, 1, 2, 3, 4, 5, 6}; + int size = sizeof(vals) / sizeof(vals[0]); + Vertex **v = valsToVets(vals, size); + Vertex *edges[][2] = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[5]}, {v[4], v[5]}, {v[5], v[6]}}; + int egdeSize = sizeof(edges) / sizeof(edges[0]); + GraphAdjList *graph = newGraphAdjList(); + // すべての頂点と辺を追加 + for (int i = 0; i < size; i++) { + addVertex(graph, v[i]); + } + for (int i = 0; i < egdeSize; i++) { + addEdge(graph, edges[i][0], edges[i][1]); + } + printf("\n初期化後のグラフは\n"); + printGraph(graph); + + // 深さ優先探索 + Vertex *res[MAX_SIZE]; + int resSize = 0; + graphDFS(graph, v[0], res, &resSize); + printf("\n深さ優先探索(DFS)の頂点列は次のとおりです\n"); + printArray(vetsToVals(res, resSize), resSize); + + // メモリを解放する + delGraphAdjList(graph); + free(v); + return 0; +} diff --git a/ja/codes/c/chapter_greedy/CMakeLists.txt b/ja/codes/c/chapter_greedy/CMakeLists.txt new file mode 100644 index 000000000..b8e6ca425 --- /dev/null +++ b/ja/codes/c/chapter_greedy/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(coin_change_greedy coin_change_greedy.c) +add_executable(fractional_knapsack fractional_knapsack.c) +add_executable(max_capacity max_capacity.c) +add_executable(max_product_cutting max_product_cutting.c) + +if (NOT CMAKE_C_COMPILER_ID STREQUAL "MSVC") + target_link_libraries(max_product_cutting m) +endif() diff --git a/ja/codes/c/chapter_greedy/coin_change_greedy.c b/ja/codes/c/chapter_greedy/coin_change_greedy.c new file mode 100644 index 000000000..ada4831e8 --- /dev/null +++ b/ja/codes/c/chapter_greedy/coin_change_greedy.c @@ -0,0 +1,60 @@ +/** + * File: coin_change_greedy.c + * Created Time: 2023-09-07 + * Author: lwbaptx (lwbaptx@gmail.com) + */ + +#include "../utils/common.h" + +/* コイン交換:貪欲法 */ +int coinChangeGreedy(int *coins, int size, int amt) { + // coins リストはソート済みと仮定する + int i = size - 1; + int count = 0; + // 残額がなくなるまで貪欲選択を繰り返す + while (amt > 0) { + // 残額以下で最も近い硬貨を見つける + while (i > 0 && coins[i] > amt) { + i--; + } + // coins[i] を選択する + amt -= coins[i]; + count++; + } + // 実行可能な解が見つからなければ -1 を返す + return amt == 0 ? count : -1; +} + +/* Driver Code */ +int main() { + // 貪欲法:大域最適解を保証できる + int coins1[6] = {1, 5, 10, 20, 50, 100}; + int amt = 186; + int res = coinChangeGreedy(coins1, 6, amt); + printf("\ncoins = "); + printArray(coins1, 6); + printf("amt = %d\n", amt); + printf("%d を作るのに必要な最小硬貨枚数は %d です\n", amt, res); + + // 貪欲法:大域最適解を保証できない + int coins2[3] = {1, 20, 50}; + amt = 60; + res = coinChangeGreedy(coins2, 3, amt); + printf("\ncoins = "); + printArray(coins2, 3); + printf("amt = %d\n", amt); + printf("%d を作るのに必要な最小硬貨枚数は %d です\n", amt, res); + printf("実際に必要な最小枚数は 3、つまり 20 + 20 + 20 です\n"); + + // 貪欲法:大域最適解を保証できない + int coins3[3] = {1, 49, 50}; + amt = 98; + res = coinChangeGreedy(coins3, 3, amt); + printf("\ncoins = "); + printArray(coins3, 3); + printf("amt = %d\n", amt); + printf("%d を作るのに必要な最小硬貨枚数は %d です\n", amt, res); + printf("実際に必要な最小枚数は 2、つまり 49 + 49 です\n"); + + return 0; +} diff --git a/ja/codes/c/chapter_greedy/fractional_knapsack.c b/ja/codes/c/chapter_greedy/fractional_knapsack.c new file mode 100644 index 000000000..a8631ab62 --- /dev/null +++ b/ja/codes/c/chapter_greedy/fractional_knapsack.c @@ -0,0 +1,60 @@ +/** + * File: fractional_knapsack.c + * Created Time: 2023-09-14 + * Author: xianii (xianyi.xia@outlook.com) + */ + +#include "../utils/common.h" + +/* 品物 */ +typedef struct { + int w; // 品物の重さ + int v; // 品物の価値 +} Item; + +/* 価値密度でソート */ +int sortByValueDensity(const void *a, const void *b) { + Item *t1 = (Item *)a; + Item *t2 = (Item *)b; + return (float)(t1->v) / t1->w < (float)(t2->v) / t2->w; +} + +/* 分数ナップサック:貪欲法 */ +float fractionalKnapsack(int wgt[], int val[], int itemCount, int cap) { + // 重さと価値の 2 属性を持つ品物リストを作成 + Item *items = malloc(sizeof(Item) * itemCount); + for (int i = 0; i < itemCount; i++) { + items[i] = (Item){.w = wgt[i], .v = val[i]}; + } + // 単位価値 item.v / item.w の高い順にソートする + qsort(items, (size_t)itemCount, sizeof(Item), sortByValueDensity); + // 貪欲選択を繰り返す + float res = 0.0; + for (int i = 0; i < itemCount; i++) { + if (items[i].w <= cap) { + // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる + res += items[i].v; + cap -= items[i].w; + } else { + // 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる + res += (float)cap / items[i].w * items[i].v; + cap = 0; + break; + } + } + free(items); + return res; +} + +/* Driver Code */ +int main(void) { + int wgt[] = {10, 20, 30, 40, 50}; + int val[] = {50, 120, 150, 210, 240}; + int capacity = 50; + + // 貪欲法 + float res = fractionalKnapsack(wgt, val, sizeof(wgt) / sizeof(int), capacity); + printf("ナップサック容量を超えない最大の品物価値は %0.2f です\n", res); + + return 0; +} diff --git a/ja/codes/c/chapter_greedy/max_capacity.c b/ja/codes/c/chapter_greedy/max_capacity.c new file mode 100644 index 000000000..94d2ef27d --- /dev/null +++ b/ja/codes/c/chapter_greedy/max_capacity.c @@ -0,0 +1,49 @@ +/** + * File: max_capacity.c + * Created Time: 2023-09-15 + * Author: xianii (xianyi.xia@outlook.com) + */ + +#include "../utils/common.h" + +/* 最小値を求める */ +int myMin(int a, int b) { + return a < b ? a : b; +} +/* 最大値を求める */ +int myMax(int a, int b) { + return a > b ? a : b; +} + +/* 最大容量:貪欲法 */ +int maxCapacity(int ht[], int htLength) { + // i, j を初期化し、それぞれ配列の両端に置く + int i = 0; + int j = htLength - 1; + // 初期の最大容量は 0 + int res = 0; + // 2 枚の板が出会うまで貪欲選択を繰り返す + while (i < j) { + // 最大容量を更新する + int capacity = myMin(ht[i], ht[j]) * (j - i); + res = myMax(res, capacity); + // 短い方を内側へ動かす + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; +} + +/* Driver Code */ +int main(void) { + int ht[] = {3, 8, 5, 2, 7, 7, 3, 4}; + + // 貪欲法 + int res = maxCapacity(ht, sizeof(ht) / sizeof(int)); + printf("最大容量は %d です\n", res); + + return 0; +} diff --git a/ja/codes/c/chapter_greedy/max_product_cutting.c b/ja/codes/c/chapter_greedy/max_product_cutting.c new file mode 100644 index 000000000..cc521962c --- /dev/null +++ b/ja/codes/c/chapter_greedy/max_product_cutting.c @@ -0,0 +1,38 @@ +/** + * File: max_product_cutting.c + * Created Time: 2023-09-15 + * Author: xianii (xianyi.xia@outlook.com) + */ + +#include "../utils/common.h" + +/* 最大切断積:貪欲法 */ +int maxProductCutting(int n) { + // n <= 3 のときは、必ず 1 を切り出す + if (n <= 3) { + return 1 * (n - 1); + } + // 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする + int a = n / 3; + int b = n % 3; + if (b == 1) { + // 余りが 1 のときは、1 * 3 を 2 * 2 に変える + return pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // 余りが 2 のときは、そのままにする + return pow(3, a) * 2; + } + // 余りが 0 のときは、そのままにする + return pow(3, a); +} + +/* Driver Code */ +int main(void) { + int n = 58; + // 貪欲法 + int res = maxProductCutting(n); + printf("最大分割積は %d です\n", res); + + return 0; +} diff --git a/ja/codes/c/chapter_hashing/CMakeLists.txt b/ja/codes/c/chapter_hashing/CMakeLists.txt new file mode 100644 index 000000000..9ac951ae4 --- /dev/null +++ b/ja/codes/c/chapter_hashing/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(array_hash_map array_hash_map.c) +add_executable(hash_map_chaining hash_map_chaining.c) +add_executable(hash_map_open_addressing hash_map_open_addressing.c) +add_executable(simple_hash simple_hash.c) diff --git a/ja/codes/c/chapter_hashing/array_hash_map.c b/ja/codes/c/chapter_hashing/array_hash_map.c new file mode 100644 index 000000000..4fbad99a2 --- /dev/null +++ b/ja/codes/c/chapter_hashing/array_hash_map.c @@ -0,0 +1,215 @@ +/** + * File: array_hash_map.c + * Created Time: 2023-03-18 + * Author: Guanngxu (446678850@qq.com) + */ + +#include "../utils/common.h" + +/* ハッシュテーブルのデフォルトサイズ */ +#define MAX_SIZE 100 + +/* キーと値の組 int->string */ +typedef struct { + int key; + char *val; +} Pair; + +/* キーと値の組の集合 */ +typedef struct { + void *set; + int len; +} MapSet; + +/* 配列ベースのハッシュテーブル */ +typedef struct { + Pair *buckets[MAX_SIZE]; +} ArrayHashMap; + +/* コンストラクタ */ +ArrayHashMap *newArrayHashMap() { + ArrayHashMap *hmap = malloc(sizeof(ArrayHashMap)); + for (int i=0; i < MAX_SIZE; i++) { + hmap->buckets[i] = NULL; + } + return hmap; +} + +/* デストラクタ */ +void delArrayHashMap(ArrayHashMap *hmap) { + for (int i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + free(hmap->buckets[i]->val); + free(hmap->buckets[i]); + } + } + free(hmap); +} + +/* ハッシュ関数 */ +int hashFunc(int key) { + int index = key % MAX_SIZE; + return index; +} + +/* 検索操作 */ +const char *get(const ArrayHashMap *hmap, const int key) { + int index = hashFunc(key); + const Pair *Pair = hmap->buckets[index]; + if (Pair == NULL) + return NULL; + return Pair->val; +} + +/* 追加操作 */ +void put(ArrayHashMap *hmap, const int key, const char *val) { + Pair *Pair = malloc(sizeof(Pair)); + Pair->key = key; + Pair->val = malloc(strlen(val) + 1); + strcpy(Pair->val, val); + + int index = hashFunc(key); + hmap->buckets[index] = Pair; +} + +/* 削除操作 */ +void removeItem(ArrayHashMap *hmap, const int key) { + int index = hashFunc(key); + free(hmap->buckets[index]->val); + free(hmap->buckets[index]); + hmap->buckets[index] = NULL; +} + +/* すべてのキーと値のペアを取得 */ +void pairSet(ArrayHashMap *hmap, MapSet *set) { + Pair *entries; + int i = 0, index = 0; + int total = 0; + /* 有効なキーと値のペア数を集計 */ + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + total++; + } + } + entries = malloc(sizeof(Pair) * total); + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + entries[index].key = hmap->buckets[i]->key; + entries[index].val = malloc(strlen(hmap->buckets[i]->val) + 1); + strcpy(entries[index].val, hmap->buckets[i]->val); + index++; + } + } + set->set = entries; + set->len = total; +} + +/* すべてのキーを取得 */ +void keySet(ArrayHashMap *hmap, MapSet *set) { + int *keys; + int i = 0, index = 0; + int total = 0; + /* 有効なキーと値のペア数を集計 */ + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + total++; + } + } + keys = malloc(total * sizeof(int)); + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + keys[index] = hmap->buckets[i]->key; + index++; + } + } + set->set = keys; + set->len = total; +} + +/* すべての値を取得 */ +void valueSet(ArrayHashMap *hmap, MapSet *set) { + char **vals; + int i = 0, index = 0; + int total = 0; + /* 有効なキーと値のペア数を集計 */ + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + total++; + } + } + vals = malloc(total * sizeof(char *)); + for (i = 0; i < MAX_SIZE; i++) { + if (hmap->buckets[i] != NULL) { + vals[index] = hmap->buckets[i]->val; + index++; + } + } + set->set = vals; + set->len = total; +} + +/* ハッシュテーブルを出力 */ +void print(ArrayHashMap *hmap) { + int i; + MapSet set; + pairSet(hmap, &set); + Pair *entries = (Pair *)set.set; + for (i = 0; i < set.len; i++) { + printf("%d -> %s\n", entries[i].key, entries[i].val); + } + free(set.set); +} + +/* Driver Code */ +int main() { + /* ハッシュテーブルを初期化 */ + ArrayHashMap *hmap = newArrayHashMap(); + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + put(hmap, 12836, "シャオハー"); + put(hmap, 15937, "シャオルオ"); + put(hmap, 16750, "シャオスワン"); + put(hmap, 13276, "シャオファー"); + put(hmap, 10583, "シャオヤー"); + printf("\n追加完了後、ハッシュテーブルは\nKey -> Value\n"); + print(hmap); + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + const char *name = get(hmap, 15937); + printf("\n学籍番号 15937 を入力すると、名前 %s が見つかりました\n", name); + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + removeItem(hmap, 10583); + printf("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value\n"); + print(hmap); + + /* ハッシュテーブルを走査 */ + int i; + + printf("\nキーと値のペア Key->Value を走査\n"); + print(hmap); + + MapSet set; + + keySet(hmap, &set); + int *keys = (int *)set.set; + printf("\nキー Key のみを個別に走査\n"); + for (i = 0; i < set.len; i++) { + printf("%d\n", keys[i]); + } + free(set.set); + + valueSet(hmap, &set); + char **vals = (char **)set.set; + printf("\nValue のみを個別に走査\n"); + for (i = 0; i < set.len; i++) { + printf("%s\n", vals[i]); + } + free(set.set); + + delArrayHashMap(hmap); + return 0; +} diff --git a/ja/codes/c/chapter_hashing/hash_map_chaining.c b/ja/codes/c/chapter_hashing/hash_map_chaining.c new file mode 100644 index 000000000..a2ad0f2a2 --- /dev/null +++ b/ja/codes/c/chapter_hashing/hash_map_chaining.c @@ -0,0 +1,213 @@ +/** + * File: hash_map_chaining.c + * Created Time: 2023-10-13 + * Author: SenMing (1206575349@qq.com), krahets (krahets@163.com) + */ + +#include +#include +#include + +// val の最大長を 100 と仮定する +#define MAX_SIZE 100 + +/* キーと値の組 */ +typedef struct { + int key; + char val[MAX_SIZE]; +} Pair; + +/* 連結リストノード */ +typedef struct Node { + Pair *pair; + struct Node *next; +} Node; + +/* チェイン法ハッシュテーブル */ +typedef struct { + int size; // キーと値のペア数 + int capacity; // ハッシュテーブル容量 + double loadThres; // リサイズを発動する負荷率のしきい値 + int extendRatio; // 拡張倍率 + Node **buckets; // バケット配列 +} HashMapChaining; + +/* コンストラクタ */ +HashMapChaining *newHashMapChaining() { + HashMapChaining *hashMap = (HashMapChaining *)malloc(sizeof(HashMapChaining)); + hashMap->size = 0; + hashMap->capacity = 4; + hashMap->loadThres = 2.0 / 3.0; + hashMap->extendRatio = 2; + hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); + for (int i = 0; i < hashMap->capacity; i++) { + hashMap->buckets[i] = NULL; + } + return hashMap; +} + +/* デストラクタ */ +void delHashMapChaining(HashMapChaining *hashMap) { + for (int i = 0; i < hashMap->capacity; i++) { + Node *cur = hashMap->buckets[i]; + while (cur) { + Node *tmp = cur; + cur = cur->next; + free(tmp->pair); + free(tmp); + } + } + free(hashMap->buckets); + free(hashMap); +} + +/* ハッシュ関数 */ +int hashFunc(HashMapChaining *hashMap, int key) { + return key % hashMap->capacity; +} + +/* 負荷率 */ +double loadFactor(HashMapChaining *hashMap) { + return (double)hashMap->size / (double)hashMap->capacity; +} + +/* 検索操作 */ +char *get(HashMapChaining *hashMap, int key) { + int index = hashFunc(hashMap, key); + // バケットを走査し、key が見つかれば対応する val を返す + Node *cur = hashMap->buckets[index]; + while (cur) { + if (cur->pair->key == key) { + return cur->pair->val; + } + cur = cur->next; + } + return ""; // key が見つからない場合は空文字列を返す +} + +/* 追加操作 */ +void put(HashMapChaining *hashMap, int key, const char *val); + +/* ハッシュテーブルを拡張 */ +void extend(HashMapChaining *hashMap) { + // 元のハッシュテーブルを一時保存 + int oldCapacity = hashMap->capacity; + Node **oldBuckets = hashMap->buckets; + // リサイズ後の新しいハッシュテーブルを初期化 + hashMap->capacity *= hashMap->extendRatio; + hashMap->buckets = (Node **)malloc(hashMap->capacity * sizeof(Node *)); + for (int i = 0; i < hashMap->capacity; i++) { + hashMap->buckets[i] = NULL; + } + hashMap->size = 0; + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す + for (int i = 0; i < oldCapacity; i++) { + Node *cur = oldBuckets[i]; + while (cur) { + put(hashMap, cur->pair->key, cur->pair->val); + Node *temp = cur; + cur = cur->next; + // メモリを解放する + free(temp->pair); + free(temp); + } + } + + free(oldBuckets); +} + +/* 追加操作 */ +void put(HashMapChaining *hashMap, int key, const char *val) { + // 負荷率がしきい値を超えたら、リサイズを実行 + if (loadFactor(hashMap) > hashMap->loadThres) { + extend(hashMap); + } + int index = hashFunc(hashMap, key); + // バケットを走査し、指定した key が見つかれば対応する val を更新して返す + Node *cur = hashMap->buckets[index]; + while (cur) { + if (cur->pair->key == key) { + strcpy(cur->pair->val, val); // 指定した `key` に遭遇したら、対応する `val` を更新して返す + return; + } + cur = cur->next; + } + // 該当する `key` がなければ、キーと値のペアを連結リストの先頭に追加する + Pair *newPair = (Pair *)malloc(sizeof(Pair)); + newPair->key = key; + strcpy(newPair->val, val); + Node *newNode = (Node *)malloc(sizeof(Node)); + newNode->pair = newPair; + newNode->next = hashMap->buckets[index]; + hashMap->buckets[index] = newNode; + hashMap->size++; +} + +/* 削除操作 */ +void removeItem(HashMapChaining *hashMap, int key) { + int index = hashFunc(hashMap, key); + Node *cur = hashMap->buckets[index]; + Node *pre = NULL; + while (cur) { + if (cur->pair->key == key) { + // そこからキーと値の組を削除する + if (pre) { + pre->next = cur->next; + } else { + hashMap->buckets[index] = cur->next; + } + // メモリを解放する + free(cur->pair); + free(cur); + hashMap->size--; + return; + } + pre = cur; + cur = cur->next; + } +} + +/* ハッシュテーブルを出力 */ +void print(HashMapChaining *hashMap) { + for (int i = 0; i < hashMap->capacity; i++) { + Node *cur = hashMap->buckets[i]; + printf("["); + while (cur) { + printf("%d -> %s, ", cur->pair->key, cur->pair->val); + cur = cur->next; + } + printf("]\n"); + } +} + +/* Driver Code */ +int main() { + /* ハッシュテーブルを初期化 */ + HashMapChaining *hashMap = newHashMapChaining(); + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + put(hashMap, 12836, "シャオハー"); + put(hashMap, 15937, "シャオルオ"); + put(hashMap, 16750, "シャオスワン"); + put(hashMap, 13276, "シャオファー"); + put(hashMap, 10583, "シャオヤー"); + printf("\n追加完了後、ハッシュテーブルは\nKey -> Value\n"); + print(hashMap); + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + char *name = get(hashMap, 13276); + printf("\n学籍番号 13276 を入力すると、名前 %s が見つかりました\n", name); + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + removeItem(hashMap, 12836); + printf("\n学籍番号 12836 を削除した後、ハッシュテーブルは\nKey -> Value\n"); + print(hashMap); + + /* ハッシュテーブルの領域を解放する */ + delHashMapChaining(hashMap); + + return 0; +} diff --git a/ja/codes/c/chapter_hashing/hash_map_open_addressing.c b/ja/codes/c/chapter_hashing/hash_map_open_addressing.c new file mode 100644 index 000000000..ab3cf29ea --- /dev/null +++ b/ja/codes/c/chapter_hashing/hash_map_open_addressing.c @@ -0,0 +1,211 @@ +/** + * File: hash_map_open_addressing.c + * Created Time: 2023-10-6 + * Author: lclc6 (w1929522410@163.com) + */ + +#include "../utils/common.h" + +/* オープンアドレス法ハッシュテーブル */ +typedef struct { + int key; + char *val; +} Pair; + +/* オープンアドレス法ハッシュテーブル */ +typedef struct { + int size; // キーと値のペア数 + int capacity; // ハッシュテーブル容量 + double loadThres; // リサイズを発動する負荷率のしきい値 + int extendRatio; // 拡張倍率 + Pair **buckets; // バケット配列 + Pair *TOMBSTONE; // 削除済みマーク +} HashMapOpenAddressing; + +// 関数宣言 +void extend(HashMapOpenAddressing *hashMap); + +/* コンストラクタ */ +HashMapOpenAddressing *newHashMapOpenAddressing() { + HashMapOpenAddressing *hashMap = (HashMapOpenAddressing *)malloc(sizeof(HashMapOpenAddressing)); + hashMap->size = 0; + hashMap->capacity = 4; + hashMap->loadThres = 2.0 / 3.0; + hashMap->extendRatio = 2; + hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *)); + hashMap->TOMBSTONE = (Pair *)malloc(sizeof(Pair)); + hashMap->TOMBSTONE->key = -1; + hashMap->TOMBSTONE->val = "-1"; + + return hashMap; +} + +/* デストラクタ */ +void delHashMapOpenAddressing(HashMapOpenAddressing *hashMap) { + for (int i = 0; i < hashMap->capacity; i++) { + Pair *pair = hashMap->buckets[i]; + if (pair != NULL && pair != hashMap->TOMBSTONE) { + free(pair->val); + free(pair); + } + } + free(hashMap->buckets); + free(hashMap->TOMBSTONE); + free(hashMap); +} + +/* ハッシュ関数 */ +int hashFunc(HashMapOpenAddressing *hashMap, int key) { + return key % hashMap->capacity; +} + +/* 負荷率 */ +double loadFactor(HashMapOpenAddressing *hashMap) { + return (double)hashMap->size / (double)hashMap->capacity; +} + +/* key に対応するバケットインデックスを探す */ +int findBucket(HashMapOpenAddressing *hashMap, int key) { + int index = hashFunc(hashMap, key); + int firstTombstone = -1; + // 線形プロービングを行い、空バケットに達したら終了 + while (hashMap->buckets[index] != NULL) { + // key が見つかったら、対応するバケットのインデックスを返す + if (hashMap->buckets[index]->key == key) { + // 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 + if (firstTombstone != -1) { + hashMap->buckets[firstTombstone] = hashMap->buckets[index]; + hashMap->buckets[index] = hashMap->TOMBSTONE; + return firstTombstone; // 移動後のバケットインデックスを返す + } + return index; // バケットのインデックスを返す + } + // 最初に見つかった削除マークを記録 + if (firstTombstone == -1 && hashMap->buckets[index] == hashMap->TOMBSTONE) { + firstTombstone = index; + } + // バケットのインデックスを計算し、末尾を越えたら先頭に戻る + index = (index + 1) % hashMap->capacity; + } + // key が存在しない場合は追加位置のインデックスを返す + return firstTombstone == -1 ? index : firstTombstone; +} + +/* 検索操作 */ +char *get(HashMapOpenAddressing *hashMap, int key) { + // key に対応するバケットインデックスを探す + int index = findBucket(hashMap, key); + // キーと値の組が見つかったら、対応する val を返す + if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { + return hashMap->buckets[index]->val; + } + // キーと値の組が存在しない場合は空文字列を返す + return ""; +} + +/* 追加操作 */ +void put(HashMapOpenAddressing *hashMap, int key, char *val) { + // 負荷率がしきい値を超えたら、リサイズを実行 + if (loadFactor(hashMap) > hashMap->loadThres) { + extend(hashMap); + } + // key に対応するバケットインデックスを探す + int index = findBucket(hashMap, key); + // キーと値の組が見つかったら、val を上書きして返す + if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { + free(hashMap->buckets[index]->val); + hashMap->buckets[index]->val = (char *)malloc(sizeof(strlen(val) + 1)); + strcpy(hashMap->buckets[index]->val, val); + hashMap->buckets[index]->val[strlen(val)] = '\0'; + return; + } + // キーと値の組が存在しない場合は、その組を追加する + Pair *pair = (Pair *)malloc(sizeof(Pair)); + pair->key = key; + pair->val = (char *)malloc(sizeof(strlen(val) + 1)); + strcpy(pair->val, val); + pair->val[strlen(val)] = '\0'; + + hashMap->buckets[index] = pair; + hashMap->size++; +} + +/* 削除操作 */ +void removeItem(HashMapOpenAddressing *hashMap, int key) { + // key に対応するバケットインデックスを探す + int index = findBucket(hashMap, key); + // キーと値の組が見つかったら、削除マーカーで上書きする + if (hashMap->buckets[index] != NULL && hashMap->buckets[index] != hashMap->TOMBSTONE) { + Pair *pair = hashMap->buckets[index]; + free(pair->val); + free(pair); + hashMap->buckets[index] = hashMap->TOMBSTONE; + hashMap->size--; + } +} + +/* ハッシュテーブルを拡張 */ +void extend(HashMapOpenAddressing *hashMap) { + // 元のハッシュテーブルを一時保存 + Pair **bucketsTmp = hashMap->buckets; + int oldCapacity = hashMap->capacity; + // リサイズ後の新しいハッシュテーブルを初期化 + hashMap->capacity *= hashMap->extendRatio; + hashMap->buckets = (Pair **)calloc(hashMap->capacity, sizeof(Pair *)); + hashMap->size = 0; + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す + for (int i = 0; i < oldCapacity; i++) { + Pair *pair = bucketsTmp[i]; + if (pair != NULL && pair != hashMap->TOMBSTONE) { + put(hashMap, pair->key, pair->val); + free(pair->val); + free(pair); + } + } + free(bucketsTmp); +} + +/* ハッシュテーブルを出力 */ +void print(HashMapOpenAddressing *hashMap) { + for (int i = 0; i < hashMap->capacity; i++) { + Pair *pair = hashMap->buckets[i]; + if (pair == NULL) { + printf("NULL\n"); + } else if (pair == hashMap->TOMBSTONE) { + printf("TOMBSTONE\n"); + } else { + printf("%d -> %s\n", pair->key, pair->val); + } + } +} + +/* Driver Code */ +int main() { + // ハッシュテーブルを初期化 + HashMapOpenAddressing *hashmap = newHashMapOpenAddressing(); + + // 追加操作 + // ハッシュテーブルにキーと値の組 (key, val) を追加する + put(hashmap, 12836, "シャオハー"); + put(hashmap, 15937, "シャオルオ"); + put(hashmap, 16750, "シャオスワン"); + put(hashmap, 13276, "シャオファー"); + put(hashmap, 10583, "シャオヤー"); + printf("\n追加完了後、ハッシュテーブルは\nKey -> Value\n"); + print(hashmap); + + // 検索操作 + // ハッシュテーブルにキー key を入力し、値 val を得る + char *name = get(hashmap, 13276); + printf("\n学籍番号 13276 を入力すると、名前 %s が見つかりました\n", name); + + // 削除操作 + // ハッシュテーブルからキーと値の組 (key, val) を削除する + removeItem(hashmap, 16750); + printf("\n16750 を削除した後、ハッシュテーブルは\nKey -> Value\n"); + print(hashmap); + + // ハッシュテーブルを破棄する + delHashMapOpenAddressing(hashmap); + return 0; +} diff --git a/ja/codes/c/chapter_hashing/simple_hash.c b/ja/codes/c/chapter_hashing/simple_hash.c new file mode 100644 index 000000000..af78de908 --- /dev/null +++ b/ja/codes/c/chapter_hashing/simple_hash.c @@ -0,0 +1,68 @@ +/** + * File: simple_hash.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 加算ハッシュ */ +int addHash(char *key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (int i = 0; i < strlen(key); i++) { + hash = (hash + (unsigned char)key[i]) % MODULUS; + } + return (int)hash; +} + +/* 乗算ハッシュ */ +int mulHash(char *key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (int i = 0; i < strlen(key); i++) { + hash = (31 * hash + (unsigned char)key[i]) % MODULUS; + } + return (int)hash; +} + +/* XOR ハッシュ */ +int xorHash(char *key) { + int hash = 0; + const int MODULUS = 1000000007; + + for (int i = 0; i < strlen(key); i++) { + hash ^= (unsigned char)key[i]; + } + return hash & MODULUS; +} + +/* 回転ハッシュ */ +int rotHash(char *key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (int i = 0; i < strlen(key); i++) { + hash = ((hash << 4) ^ (hash >> 28) ^ (unsigned char)key[i]) % MODULUS; + } + + return (int)hash; +} + +/* Driver Code */ +int main() { + char *key = "Hello アルゴリズム"; + + int hash = addHash(key); + printf("加算ハッシュ値は %d\n", hash); + + hash = mulHash(key); + printf("乗算ハッシュ値は %d\n", hash); + + hash = xorHash(key); + printf("XORハッシュ値は %d\n", hash); + + hash = rotHash(key); + printf("回転ハッシュ値は %d\n", hash); + + return 0; +} diff --git a/ja/codes/c/chapter_heap/CMakeLists.txt b/ja/codes/c/chapter_heap/CMakeLists.txt new file mode 100644 index 000000000..357ec702c --- /dev/null +++ b/ja/codes/c/chapter_heap/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(my_heap_test my_heap_test.c) +add_executable(top_k top_k.c) diff --git a/ja/codes/c/chapter_heap/my_heap.c b/ja/codes/c/chapter_heap/my_heap.c new file mode 100644 index 000000000..88f07edb5 --- /dev/null +++ b/ja/codes/c/chapter_heap/my_heap.c @@ -0,0 +1,152 @@ +/** + * File: my_heap.c + * Created Time: 2023-01-15 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 5000 + +/* 最大ヒープ */ +typedef struct { + // size は実際の要素数を表す + int size; + // あらかじめメモリを確保した配列を使い、拡張を避ける + int data[MAX_SIZE]; +} MaxHeap; + +// 関数宣言 +void siftDown(MaxHeap *maxHeap, int i); +void siftUp(MaxHeap *maxHeap, int i); +int parent(MaxHeap *maxHeap, int i); + +/* コンストラクタ。スライスからヒープを構築する */ +MaxHeap *newMaxHeap(int nums[], int size) { + // すべての要素をヒープに入れる + MaxHeap *maxHeap = (MaxHeap *)malloc(sizeof(MaxHeap)); + maxHeap->size = size; + memcpy(maxHeap->data, nums, size * sizeof(int)); + for (int i = parent(maxHeap, size - 1); i >= 0; i--) { + // 葉ノード以外のすべてのノードをヒープ化 + siftDown(maxHeap, i); + } + return maxHeap; +} + +/* デストラクタ */ +void delMaxHeap(MaxHeap *maxHeap) { + // メモリを解放する + free(maxHeap); +} + +/* 左子ノードのインデックスを取得 */ +int left(MaxHeap *maxHeap, int i) { + return 2 * i + 1; +} + +/* 右子ノードのインデックスを取得 */ +int right(MaxHeap *maxHeap, int i) { + return 2 * i + 2; +} + +/* 親ノードのインデックスを取得 */ +int parent(MaxHeap *maxHeap, int i) { + return (i - 1) / 2; // 切り捨て +} + +/* 要素を交換 */ +void swap(MaxHeap *maxHeap, int i, int j) { + int temp = maxHeap->data[i]; + maxHeap->data[i] = maxHeap->data[j]; + maxHeap->data[j] = temp; +} + +/* ヒープのサイズを取得 */ +int size(MaxHeap *maxHeap) { + return maxHeap->size; +} + +/* ヒープが空かどうかを判定 */ +int isEmpty(MaxHeap *maxHeap) { + return maxHeap->size == 0; +} + +/* ヒープ先頭要素にアクセス */ +int peek(MaxHeap *maxHeap) { + return maxHeap->data[0]; +} + +/* 要素をヒープに追加 */ +void push(MaxHeap *maxHeap, int val) { + // 通常は、これほど多くのノードを追加すべきではない + if (maxHeap->size == MAX_SIZE) { + printf("heap is full!"); + return; + } + // ノードを追加 + maxHeap->data[maxHeap->size] = val; + maxHeap->size++; + + // 下から上へヒープ化 + siftUp(maxHeap, maxHeap->size - 1); +} + +/* 要素をヒープから取り出す */ +int pop(MaxHeap *maxHeap) { + // 空判定の処理 + if (isEmpty(maxHeap)) { + printf("heap is empty!"); + return INT_MAX; + } + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + swap(maxHeap, 0, size(maxHeap) - 1); + // ノードを削除 + int val = maxHeap->data[maxHeap->size - 1]; + maxHeap->size--; + // 上から下へヒープ化 + siftDown(maxHeap, 0); + + // ヒープ先頭要素を返す + return val; +} + +/* ノード i から始めて、上から下へヒープ化 */ +void siftDown(MaxHeap *maxHeap, int i) { + while (true) { + // ノード i, l, r のうち値が最大のノードを max とする + int l = left(maxHeap, i); + int r = right(maxHeap, i); + int max = i; + if (l < size(maxHeap) && maxHeap->data[l] > maxHeap->data[max]) { + max = l; + } + if (r < size(maxHeap) && maxHeap->data[r] > maxHeap->data[max]) { + max = r; + } + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + if (max == i) { + break; + } + // 2 つのノードを交換 + swap(maxHeap, i, max); + // ループで上から下へヒープ化 + i = max; + } +} + +/* ノード i から始めて、下から上へヒープ化 */ +void siftUp(MaxHeap *maxHeap, int i) { + while (true) { + // ノード i の親ノードを取得 + int p = parent(maxHeap, i); + // 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 + if (p < 0 || maxHeap->data[i] <= maxHeap->data[p]) { + break; + } + // 2 つのノードを交換 + swap(maxHeap, i, p); + // ループで下から上へヒープ化 + i = p; + } +} diff --git a/ja/codes/c/chapter_heap/my_heap_test.c b/ja/codes/c/chapter_heap/my_heap_test.c new file mode 100644 index 000000000..ef9fe0955 --- /dev/null +++ b/ja/codes/c/chapter_heap/my_heap_test.c @@ -0,0 +1,41 @@ +/** + * File: my_heap_test.c + * Created Time: 2023-01-15 + * Author: Reanon (793584285@qq.com) + */ + +#include "my_heap.c" + +/* Driver Code */ +int main() { + /* ヒープを初期化 */ + // 最大ヒープを初期化 + int nums[] = {9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}; + MaxHeap *maxHeap = newMaxHeap(nums, sizeof(nums) / sizeof(int)); + printf("配列を入力してヒープ化した後\n"); + printHeap(maxHeap->data, maxHeap->size); + + /* ヒープ頂点の要素を取得 */ + printf("\nヒープの先頭要素は %d\n", peek(maxHeap)); + + /* 要素をヒープに追加 */ + push(maxHeap, 7); + printf("\n要素 7 をヒープに追加した後\n"); + printHeap(maxHeap->data, maxHeap->size); + + /* ヒープ頂点の要素を取り出す */ + int top = pop(maxHeap); + printf("\nヒープの先頭要素 %d を取り出した後\n", top); + printHeap(maxHeap->data, maxHeap->size); + + /* ヒープのサイズを取得 */ + printf("\nヒープの要素数は %d\n", size(maxHeap)); + + /* ヒープが空かどうかを判定 */ + printf("\nヒープが空かどうか %d\n", isEmpty(maxHeap)); + + // メモリを解放する + delMaxHeap(maxHeap); + + return 0; +} diff --git a/ja/codes/c/chapter_heap/top_k.c b/ja/codes/c/chapter_heap/top_k.c new file mode 100644 index 000000000..da1630109 --- /dev/null +++ b/ja/codes/c/chapter_heap/top_k.c @@ -0,0 +1,73 @@ +/** + * File: top_k.c + * Created Time: 2023-10-26 + * Author: krahets (krahets163.com) + */ + +#include "my_heap.c" + +/* 要素をヒープに追加 */ +void pushMinHeap(MaxHeap *maxHeap, int val) { + // 要素を反転する + push(maxHeap, -val); +} + +/* 要素をヒープから取り出す */ +int popMinHeap(MaxHeap *maxHeap) { + // 要素を反転する + return -pop(maxHeap); +} + +/* ヒープ先頭要素にアクセス */ +int peekMinHeap(MaxHeap *maxHeap) { + // 要素を反転する + return -peek(maxHeap); +} + +/* ヒープから要素を取り出す */ +int *getMinHeap(MaxHeap *maxHeap) { + // ヒープ内のすべての要素を反転して res 配列に格納 + int *res = (int *)malloc(maxHeap->size * sizeof(int)); + for (int i = 0; i < maxHeap->size; i++) { + res[i] = -maxHeap->data[i]; + } + return res; +} + +// ヒープに基づいて配列中の最大の k 個の要素を求める関数 +int *topKHeap(int *nums, int sizeNums, int k) { + // 最小ヒープを初期化する + // 注意: ヒープ内の全要素を反転し、最大ヒープで最小ヒープをシミュレートする + int *empty = (int *)malloc(0); + MaxHeap *maxHeap = newMaxHeap(empty, 0); + // 配列の先頭 k 個の要素をヒープに追加 + for (int i = 0; i < k; i++) { + pushMinHeap(maxHeap, nums[i]); + } + // k+1 番目の要素から開始し、ヒープ長を k に保つ + for (int i = k; i < sizeNums; i++) { + // 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する + if (nums[i] > peekMinHeap(maxHeap)) { + popMinHeap(maxHeap); + pushMinHeap(maxHeap, nums[i]); + } + } + int *res = getMinHeap(maxHeap); + // メモリを解放する + delMaxHeap(maxHeap); + return res; +} + +/* Driver Code */ +int main() { + int nums[] = {1, 7, 6, 3, 2}; + int k = 3; + int sizeNums = sizeof(nums) / sizeof(nums[0]); + + int *res = topKHeap(nums, sizeNums, k); + printf("最大の %d 個の要素は: ", k); + printArray(res, k); + + free(res); + return 0; +} diff --git a/ja/codes/c/chapter_searching/CMakeLists.txt b/ja/codes/c/chapter_searching/CMakeLists.txt new file mode 100644 index 000000000..7b2a152d5 --- /dev/null +++ b/ja/codes/c/chapter_searching/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(binary_search binary_search.c) +add_executable(two_sum two_sum.c) +add_executable(binary_search_edge binary_search_edge.c) +add_executable(binary_search_insertion binary_search_insertion.c) diff --git a/ja/codes/c/chapter_searching/binary_search.c b/ja/codes/c/chapter_searching/binary_search.c new file mode 100644 index 000000000..1f09e2bfb --- /dev/null +++ b/ja/codes/c/chapter_searching/binary_search.c @@ -0,0 +1,59 @@ +/** + * File: binary_search.c + * Created Time: 2023-03-18 + * Author: Guanngxu (446678850@qq.com) + */ + +#include "../utils/common.h" + +/* 二分探索(両閉区間) */ +int binarySearch(int *nums, int len, int target) { + // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す + int i = 0, j = len - 1; + // ループし、探索区間が空になったら終了する(i > j で空) + while (i <= j) { + int m = i + (j - i) / 2; // 中点インデックス m を計算 + if (nums[m] < target) // この場合、target は区間 [m+1, j] にある + i = m + 1; + else if (nums[m] > target) // この場合、target は区間 [i, m-1] にある + j = m - 1; + else // 目標要素が見つかったらそのインデックスを返す + return m; + } + // 目標要素が見つからなければ -1 を返す + return -1; +} + +/* 二分探索(左閉右開区間) */ +int binarySearchLCRO(int *nums, int len, int target) { + // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す + int i = 0, j = len; + // ループし、探索区間が空になったら終了する(i = j で空) + while (i < j) { + int m = i + (j - i) / 2; // 中点インデックス m を計算 + if (nums[m] < target) // この場合、target は区間 [m+1, j) にある + i = m + 1; + else if (nums[m] > target) // この場合、target は区間 [i, m) にある + j = m; + else // 目標要素が見つかったらそのインデックスを返す + return m; + } + // 目標要素が見つからなければ -1 を返す + return -1; +} + +/* Driver Code */ +int main() { + int target = 6; + int nums[10] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + + /* 二分探索(両閉区間) */ + int index = binarySearch(nums, 10, target); + printf("対象要素 6 のインデックス = %d\n", index); + + /* 二分探索(左閉右開区間) */ + index = binarySearchLCRO(nums, 10, target); + printf("対象要素 6 のインデックス = %d\n", index); + + return 0; +} diff --git a/ja/codes/c/chapter_searching/binary_search_edge.c b/ja/codes/c/chapter_searching/binary_search_edge.c new file mode 100644 index 000000000..485563631 --- /dev/null +++ b/ja/codes/c/chapter_searching/binary_search_edge.c @@ -0,0 +1,67 @@ +/** + * File: binary_search_edge.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 二分探索で挿入位置を探す(重複要素あり) */ +int binarySearchInsertion(int *nums, int numSize, int target) { + int i = 0, j = numSize - 1; // 両閉区間 [0, n-1] を初期化 + while (i <= j) { + int m = i + (j - i) / 2; // 中点インデックス m を計算 + if (nums[m] < target) { + i = m + 1; // target は区間 [m+1, j] にある + } else { + j = m - 1; // target より小さい最初の要素は区間 [i, m-1] にある + } + } + // 挿入位置 i を返す + return i; +} + +/* 最も左の target を二分探索 */ +int binarySearchLeftEdge(int *nums, int numSize, int target) { + // target の挿入位置を探すのと等価 + int i = binarySearchInsertion(nums, numSize, target); + // target が見つからなければ、-1 を返す + if (i == numSize || nums[i] != target) { + return -1; + } + // target が見つかったら、インデックス i を返す + return i; +} + +/* 最も右の target を二分探索 */ +int binarySearchRightEdge(int *nums, int numSize, int target) { + // 最左の target + 1 を探す問題に変換する + int i = binarySearchInsertion(nums, numSize, target + 1); + // j は最も右の target を指し、i は target より大きい最初の要素を指す + int j = i - 1; + // target が見つからなければ、-1 を返す + if (j == -1 || nums[j] != target) { + return -1; + } + // target が見つかったら、インデックス j を返す + return j; +} + +/* Driver Code */ +int main() { + // 重複要素を含む配列 + int nums[] = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; + printf("\n配列 nums = "); + printArray(nums, sizeof(nums) / sizeof(nums[0])); + + // 二分探索で左端と右端を探す + int targets[] = {6, 7}; + for (int i = 0; i < sizeof(targets) / sizeof(targets[0]); i++) { + int index = binarySearchLeftEdge(nums, sizeof(nums) / sizeof(nums[0]), targets[i]); + printf("最も左の要素 %d のインデックスは %d\n", targets[i], index); + index = binarySearchRightEdge(nums, sizeof(nums) / sizeof(nums[0]), targets[i]); + printf("最も右の要素 %d のインデックスは %d\n", targets[i], index); + } + + return 0; +} \ No newline at end of file diff --git a/ja/codes/c/chapter_searching/binary_search_insertion.c b/ja/codes/c/chapter_searching/binary_search_insertion.c new file mode 100644 index 000000000..a980ee5a0 --- /dev/null +++ b/ja/codes/c/chapter_searching/binary_search_insertion.c @@ -0,0 +1,68 @@ +/** + * File: binary_search_insertion.c + * Created Time: 2023-09-09 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 二分探索で挿入位置を探す(重複要素なし) */ +int binarySearchInsertionSimple(int *nums, int numSize, int target) { + int i = 0, j = numSize - 1; // 両閉区間 [0, n-1] を初期化 + while (i <= j) { + int m = i + (j - i) / 2; // 中点インデックス m を計算 + if (nums[m] < target) { + i = m + 1; // target は区間 [m+1, j] にある + } else if (nums[m] > target) { + j = m - 1; // target は区間 [i, m-1] にある + } else { + return m; // target が見つかったら、挿入位置 m を返す + } + } + // target が見つからなければ、挿入位置 i を返す + return i; +} + +/* 二分探索で挿入位置を探す(重複要素あり) */ +int binarySearchInsertion(int *nums, int numSize, int target) { + int i = 0, j = numSize - 1; // 両閉区間 [0, n-1] を初期化 + while (i <= j) { + int m = i + (j - i) / 2; // 中点インデックス m を計算 + if (nums[m] < target) { + i = m + 1; // target は区間 [m+1, j] にある + } else if (nums[m] > target) { + j = m - 1; // target は区間 [i, m-1] にある + } else { + j = m - 1; // target より小さい最初の要素は区間 [i, m-1] にある + } + } + // 挿入位置 i を返す + return i; +} + +/* Driver Code */ +int main() { + // 重複要素のない配列 + int nums1[] = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; + printf("\n配列 nums = "); + printArray(nums1, sizeof(nums1) / sizeof(nums1[0])); + // 二分探索で挿入位置を探す + int targets1[] = {6, 9}; + for (int i = 0; i < sizeof(targets1) / sizeof(targets1[0]); i++) { + int index = binarySearchInsertionSimple(nums1, sizeof(nums1) / sizeof(nums1[0]), targets1[i]); + printf("要素 %d の挿入位置のインデックスは %d\n", targets1[i], index); + } + + // 重複要素を含む配列 + int nums2[] = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; + printf("\n配列 nums = "); + printArray(nums2, sizeof(nums2) / sizeof(nums2[0])); + // 二分探索で挿入位置を探す + int targets2[] = {2, 6, 20}; + for (int i = 0; i < sizeof(targets2) / sizeof(int); i++) { + int index = binarySearchInsertion(nums2, sizeof(nums2) / sizeof(nums2[0]), targets2[i]); + printf("要素 %d の挿入位置のインデックスは %d\n", targets2[i], index); + } + + return 0; +} diff --git a/ja/codes/c/chapter_searching/two_sum.c b/ja/codes/c/chapter_searching/two_sum.c new file mode 100644 index 000000000..30f803525 --- /dev/null +++ b/ja/codes/c/chapter_searching/two_sum.c @@ -0,0 +1,86 @@ +/** + * File: two_sum.c + * Created Time: 2023-01-19 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* 方法 1:総当たり列挙 */ +int *twoSumBruteForce(int *nums, int numsSize, int target, int *returnSize) { + for (int i = 0; i < numsSize; ++i) { + for (int j = i + 1; j < numsSize; ++j) { + if (nums[i] + nums[j] == target) { + int *res = malloc(sizeof(int) * 2); + res[0] = i, res[1] = j; + *returnSize = 2; + return res; + } + } + } + *returnSize = 0; + return NULL; +} + +/* ハッシュテーブル */ +typedef struct { + int key; + int val; + UT_hash_handle hh; // uthash.h を用いて実装 +} HashTable; + +/* ハッシュテーブルを検索する */ +HashTable *find(HashTable *h, int key) { + HashTable *tmp; + HASH_FIND_INT(h, &key, tmp); + return tmp; +} + +/* ハッシュテーブルに要素を挿入する */ +void insert(HashTable **h, int key, int val) { + HashTable *t = find(*h, key); + if (t == NULL) { + HashTable *tmp = malloc(sizeof(HashTable)); + tmp->key = key, tmp->val = val; + HASH_ADD_INT(*h, key, tmp); + } else { + t->val = val; + } +} + +/* 方法 2:補助ハッシュテーブル */ +int *twoSumHashTable(int *nums, int numsSize, int target, int *returnSize) { + HashTable *hashtable = NULL; + for (int i = 0; i < numsSize; i++) { + HashTable *t = find(hashtable, target - nums[i]); + if (t != NULL) { + int *res = malloc(sizeof(int) * 2); + res[0] = t->val, res[1] = i; + *returnSize = 2; + return res; + } + insert(&hashtable, nums[i], i); + } + *returnSize = 0; + return NULL; +} + +/* Driver Code */ +int main() { + // ======= Test Case ======= + int nums[] = {2, 7, 11, 15}; + int target = 13; + // ====== Driver Code ====== + int returnSize; + int *res = twoSumBruteForce(nums, sizeof(nums) / sizeof(int), target, &returnSize); + // 方法 1 + printf("方法1 res = "); + printArray(res, returnSize); + + // 方法 2 + res = twoSumHashTable(nums, sizeof(nums) / sizeof(int), target, &returnSize); + printf("方法2 res = "); + printArray(res, returnSize); + + return 0; +} \ No newline at end of file diff --git a/ja/codes/c/chapter_sorting/CMakeLists.txt b/ja/codes/c/chapter_sorting/CMakeLists.txt new file mode 100644 index 000000000..88756b4c9 --- /dev/null +++ b/ja/codes/c/chapter_sorting/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(bubble_sort bubble_sort.c) +add_executable(insertion_sort insertion_sort.c) +add_executable(quick_sort quick_sort.c) +add_executable(counting_sort counting_sort.c) +add_executable(radix_sort radix_sort.c) +add_executable(merge_sort merge_sort.c) +add_executable(heap_sort heap_sort.c) +add_executable(bucket_sort bucket_sort.c) +add_executable(selection_sort selection_sort.c) diff --git a/ja/codes/c/chapter_sorting/bubble_sort.c b/ja/codes/c/chapter_sorting/bubble_sort.c new file mode 100644 index 000000000..f8c9a95fe --- /dev/null +++ b/ja/codes/c/chapter_sorting/bubble_sort.c @@ -0,0 +1,61 @@ +/** + * File: bubble_sort.c + * Created Time: 2022-12-26 + * Author: Listening (https://github.com/L-Super) + */ + +#include "../utils/common.h" + +/* バブルソート */ +void bubbleSort(int nums[], int size) { + // 外側のループ:未ソート区間は [0, i] + for (int i = size - 1; i > 0; i--) { + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + int temp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = temp; + } + } + } +} + +/* バブルソート(フラグ最適化) */ +void bubbleSortWithFlag(int nums[], int size) { + // 外側のループ:未ソート区間は [0, i] + for (int i = size - 1; i > 0; i--) { + bool flag = false; + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + int temp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = temp; + flag = true; + } + } + if (!flag) + break; + } +} + +/* Driver Code */ +int main() { + int nums[6] = {4, 1, 3, 1, 5, 2}; + printf("バブルソート後: "); + bubbleSort(nums, 6); + for (int i = 0; i < 6; i++) { + printf("%d ", nums[i]); + } + + int nums1[6] = {4, 1, 3, 1, 5, 2}; + printf("\n最適化版バブルソート後: "); + bubbleSortWithFlag(nums1, 6); + for (int i = 0; i < 6; i++) { + printf("%d ", nums1[i]); + } + printf("\n"); + + return 0; +} diff --git a/ja/codes/c/chapter_sorting/bucket_sort.c b/ja/codes/c/chapter_sorting/bucket_sort.c new file mode 100644 index 000000000..2348b0ed6 --- /dev/null +++ b/ja/codes/c/chapter_sorting/bucket_sort.c @@ -0,0 +1,57 @@ +/** + * File: bucket_sort.c + * Created Time: 2023-05-30 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define SIZE 10 + +/* `qsort` 用の比較関数 */ +int compare(const void *a, const void *b) { + float fa = *(const float *)a; + float fb = *(const float *)b; + return (fa > fb) - (fa < fb); +} + +/* バケットソート */ +void bucketSort(float nums[], int n) { + int k = n / 2; // k = n/2 個のバケットを初期化する + int *sizes = malloc(k * sizeof(int)); // 各バケットのサイズを記録する + float **buckets = malloc(k * sizeof(float *)); // 動的配列の配列(バケット) + // 各バケットに十分な容量を事前確保する + for (int i = 0; i < k; ++i) { + buckets[i] = (float *)malloc(n * sizeof(float)); + sizes[i] = 0; + } + // 1. 配列要素を各バケットに振り分ける + for (int i = 0; i < n; ++i) { + int idx = (int)(nums[i] * k); + buckets[idx][sizes[idx]++] = nums[i]; + } + // 2. 各バケットをソートする + for (int i = 0; i < k; ++i) { + qsort(buckets[i], sizes[i], sizeof(float), compare); + } + // 3. ソート済みのバケットを結合する + int idx = 0; + for (int i = 0; i < k; ++i) { + for (int j = 0; j < sizes[i]; ++j) { + nums[idx++] = buckets[i][j]; + } + // メモリを解放する + free(buckets[i]); + } +} + +/* Driver Code */ +int main() { + // 入力データは範囲 [0, 1) の浮動小数点数とする + float nums[SIZE] = {0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f}; + bucketSort(nums, SIZE); + printf("バケットソート完了後 nums = "); + printArrayFloat(nums, SIZE); + + return 0; +} diff --git a/ja/codes/c/chapter_sorting/counting_sort.c b/ja/codes/c/chapter_sorting/counting_sort.c new file mode 100644 index 000000000..bae1a71fb --- /dev/null +++ b/ja/codes/c/chapter_sorting/counting_sort.c @@ -0,0 +1,87 @@ +/** + * File: counting_sort.c + * Created Time: 2023-03-20 + * Author: Reanon (793584285@qq.com), Guanngxu (446678850@qq.com) + */ + +#include "../utils/common.h" + +/* 計数ソート */ +// 簡易実装のため、オブジェクトのソートには使えない +void countingSortNaive(int nums[], int size) { + // 1. 配列の最大要素 m を求める + int m = 0; + for (int i = 0; i < size; i++) { + if (nums[i] > m) { + m = nums[i]; + } + } + // 2. 各数値の出現回数を数える + // counter[num] は num の出現回数を表す + int *counter = calloc(m + 1, sizeof(int)); + for (int i = 0; i < size; i++) { + counter[nums[i]]++; + } + // 3. counter を走査し、各要素を元の配列 nums に書き戻す + int i = 0; + for (int num = 0; num < m + 1; num++) { + for (int j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } + // 4. メモリを解放する + free(counter); +} + +/* 計数ソート */ +// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである +void countingSort(int nums[], int size) { + // 1. 配列の最大要素 m を求める + int m = 0; + for (int i = 0; i < size; i++) { + if (nums[i] > m) { + m = nums[i]; + } + } + // 2. 各数値の出現回数を数える + // counter[num] は num の出現回数を表す + int *counter = calloc(m, sizeof(int)); + for (int i = 0; i < size; i++) { + counter[nums[i]]++; + } + // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する + // つまり counter[num]-1 は、num が res に最後に現れるインデックス + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. nums を逆順に走査し、各要素を結果配列 res に格納する + // 結果を記録するための配列 res を初期化 + int *res = malloc(sizeof(int) * size); + for (int i = size - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // num を対応するインデックスに配置 + counter[num]--; // 累積和を 1 減らして、次に num を配置するインデックスを得る + } + // 結果配列 res で元の配列 nums を上書きする + memcpy(nums, res, size * sizeof(int)); + // 5. メモリを解放する + free(res); + free(counter); +} + +/* Driver Code */ +int main() { + int nums[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + int size = sizeof(nums) / sizeof(int); + countingSortNaive(nums, size); + printf("計数ソート(オブジェクトはソート不可)完了後 nums = "); + printArray(nums, size); + + int nums1[] = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + int size1 = sizeof(nums1) / sizeof(int); + countingSort(nums1, size1); + printf("計数ソート完了後 nums1 = "); + printArray(nums1, size1); + + return 0; +} diff --git a/ja/codes/c/chapter_sorting/heap_sort.c b/ja/codes/c/chapter_sorting/heap_sort.c new file mode 100644 index 000000000..c7afd6e77 --- /dev/null +++ b/ja/codes/c/chapter_sorting/heap_sort.c @@ -0,0 +1,60 @@ +/** + * File: heap_sort.c + * Created Time: 2023-05-30 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* ヒープの長さは n。ノード i から下方向にヒープ化 */ +void siftDown(int nums[], int n, int i) { + while (1) { + // ノード i, l, r のうち値が最大のノードを ma とする + int l = 2 * i + 1; + int r = 2 * i + 2; + int ma = i; + if (l < n && nums[l] > nums[ma]) + ma = l; + if (r < n && nums[r] > nums[ma]) + ma = r; + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + if (ma == i) { + break; + } + // 2 つのノードを交換 + int temp = nums[i]; + nums[i] = nums[ma]; + nums[ma] = temp; + // ループで上から下へヒープ化 + i = ma; + } +} + +/* ヒープソート */ +void heapSort(int nums[], int n) { + // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する + for (int i = n / 2 - 1; i >= 0; --i) { + siftDown(nums, n, i); + } + // ヒープから最大要素を取り出し、n-1 回繰り返す + for (int i = n - 1; i > 0; --i) { + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + int tmp = nums[0]; + nums[0] = nums[i]; + nums[i] = tmp; + // 根ノードを起点に、上から下へヒープ化 + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +int main() { + int nums[] = {4, 1, 3, 1, 5, 2}; + int n = sizeof(nums) / sizeof(nums[0]); + + heapSort(nums, n); + printf("ヒープソート完了後 nums = "); + printArray(nums, n); + + return 0; +} \ No newline at end of file diff --git a/ja/codes/c/chapter_sorting/insertion_sort.c b/ja/codes/c/chapter_sorting/insertion_sort.c new file mode 100644 index 000000000..223e2d1bb --- /dev/null +++ b/ja/codes/c/chapter_sorting/insertion_sort.c @@ -0,0 +1,36 @@ +/** + * File: insertion_sort.c + * Created Time: 2022-12-29 + * Author: Listening (https://github.com/L-Super) + */ + +#include "../utils/common.h" + +/* 挿入ソート */ +void insertionSort(int nums[], int size) { + // 外側ループ:整列済み区間は [0, i-1] + for (int i = 1; i < size; i++) { + int base = nums[i], j = i - 1; + // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する + while (j >= 0 && nums[j] > base) { + // nums[j] を 1 つ右へ移動する + nums[j + 1] = nums[j]; + j--; + } + // base を正しい位置に配置する + nums[j + 1] = base; + } +} + +/* Driver Code */ +int main() { + int nums[] = {4, 1, 3, 1, 5, 2}; + insertionSort(nums, 6); + printf("挿入ソート完了後 nums = "); + for (int i = 0; i < 6; i++) { + printf("%d ", nums[i]); + } + printf("\n"); + + return 0; +} diff --git a/ja/codes/c/chapter_sorting/merge_sort.c b/ja/codes/c/chapter_sorting/merge_sort.c new file mode 100644 index 000000000..66d142c35 --- /dev/null +++ b/ja/codes/c/chapter_sorting/merge_sort.c @@ -0,0 +1,63 @@ +/** + * File: merge_sort.c + * Created Time: 2022-03-21 + * Author: Guanngxu (446678850@qq.com) + */ + +#include "../utils/common.h" + +/* 左部分配列と右部分配列をマージ */ +void merge(int *nums, int left, int mid, int right) { + // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] + // マージ結果を格納する一時配列 tmp を作成 + int tmpSize = right - left + 1; + int *tmp = (int *)malloc(tmpSize * sizeof(int)); + // 左右の部分配列の開始インデックスを初期化する + int i = left, j = mid + 1, k = 0; + // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) { + tmp[k++] = nums[i++]; + } else { + tmp[k++] = nums[j++]; + } + } + // 左右の部分配列の残り要素を一時配列にコピーする + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする + for (k = 0; k < tmpSize; ++k) { + nums[left + k] = tmp[k]; + } + // メモリを解放する + free(tmp); +} + +/* マージソート */ +void mergeSort(int *nums, int left, int right) { + // 終了条件 + if (left >= right) + return; // 部分配列の長さが 1 になったら再帰を終了 + // 分割フェーズ + int mid = left + (right - left) / 2; // 中点を計算 + mergeSort(nums, left, mid); // 左部分配列を再帰処理 + mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理 + // マージフェーズ + merge(nums, left, mid, right); +} + +/* Driver Code */ +int main() { + /* マージソート */ + int nums[] = {7, 3, 2, 6, 0, 1, 5, 4}; + int size = sizeof(nums) / sizeof(int); + mergeSort(nums, 0, size - 1); + printf("マージソート完了後 nums = "); + printArray(nums, size); + + return 0; +} diff --git a/ja/codes/c/chapter_sorting/quick_sort.c b/ja/codes/c/chapter_sorting/quick_sort.c new file mode 100644 index 000000000..bbf6e9a94 --- /dev/null +++ b/ja/codes/c/chapter_sorting/quick_sort.c @@ -0,0 +1,137 @@ +/** + * File: quick_sort.c + * Created Time: 2023-01-18 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* 要素の交換 */ +void swap(int nums[], int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; +} + +/* 番兵分割 */ +int partition(int nums[], int left, int right) { + // nums[left] を基準値とする + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j--; // 右から左へ基準値未満の最初の要素を探す + } + while (i < j && nums[i] <= nums[left]) { + i++; // 左から右へ基準値より大きい最初の要素を探す + } + // この 2 つの要素を交換 + swap(nums, i, j); + } + // 基準値を 2 つの部分配列の境界へ交換する + swap(nums, i, left); + // 基準値のインデックスを返す + return i; +} + +/* クイックソート */ +void quickSort(int nums[], int left, int right) { + // 部分配列の長さが 1 なら再帰を終了する + if (left >= right) { + return; + } + // 番兵分割 + int pivot = partition(nums, left, right); + // 左右の部分配列を再帰処理 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); +} + +// 以下は中央値最適化版のクイックソート + +/* 3つの候補要素の中央値を選ぶ */ +int medianThree(int nums[], int left, int mid, int right) { + int l = nums[left], m = nums[mid], r = nums[right]; + if ((l <= m && m <= r) || (r <= m && m <= l)) + return mid; // m は l と r の間 + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // l は m と r の間 + return right; +} + +/* 番兵による分割処理(3 点中央値) */ +int partitionMedian(int nums[], int left, int right) { + // 3つの候補要素の中央値を選ぶ + int med = medianThree(nums, left, (left + right) / 2, right); + // 中央値を配列の最左端に交換する + swap(nums, left, med); + // nums[left] を基準値とする + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // 右から左へ基準値未満の最初の要素を探す + while (i < j && nums[i] <= nums[left]) + i++; // 左から右へ基準値より大きい最初の要素を探す + swap(nums, i, j); // この 2 つの要素を交換 + } + swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する + return i; // 基準値のインデックスを返す +} + +/* クイックソート(三点中央値法) */ +void quickSortMedian(int nums[], int left, int right) { + // 部分配列の長さが 1 なら再帰を終了する + if (left >= right) + return; + // 番兵分割 + int pivot = partitionMedian(nums, left, right); + // 左右の部分配列を再帰処理 + quickSortMedian(nums, left, pivot - 1); + quickSortMedian(nums, pivot + 1, right); +} + +// 以下は再帰の深さを最適化したクイックソート + +/* クイックソート(再帰深度最適化) */ +void quickSortTailCall(int nums[], int left, int right) { + // 部分配列の長さが 1 なら終了 + while (left < right) { + // 番兵による分割処理 + int pivot = partition(nums, left, right); + // 2 つの部分配列のうち短いほうにクイックソートを適用する + if (pivot - left < right - pivot) { + // 左部分配列を再帰的にソート + quickSortTailCall(nums, left, pivot - 1); + // 未ソート区間の残りは [pivot + 1, right] + left = pivot + 1; + } else { + // 右部分配列を再帰的にソート + quickSortTailCall(nums, pivot + 1, right); + // 未ソート区間の残りは [left, pivot - 1] + right = pivot - 1; + } + } +} + +/* Driver Code */ +int main() { + /* クイックソート */ + int nums[] = {2, 4, 1, 0, 3, 5}; + int size = sizeof(nums) / sizeof(int); + quickSort(nums, 0, size - 1); + printf("クイックソート完了後 nums = "); + printArray(nums, size); + + /* クイックソート(中央値の基準値で最適化) */ + int nums1[] = {2, 4, 1, 0, 3, 5}; + quickSortMedian(nums1, 0, size - 1); + printf("クイックソート(中央値ピボット最適化)完了後 nums = "); + printArray(nums1, size); + + /* クイックソート(再帰深度最適化) */ + int nums2[] = {2, 4, 1, 0, 3, 5}; + quickSortTailCall(nums2, 0, size - 1); + printf("クイックソート(再帰深度最適化)完了後 nums = "); + printArray(nums1, size); + + return 0; +} diff --git a/ja/codes/c/chapter_sorting/radix_sort.c b/ja/codes/c/chapter_sorting/radix_sort.c new file mode 100644 index 000000000..0b4ebc2c8 --- /dev/null +++ b/ja/codes/c/chapter_sorting/radix_sort.c @@ -0,0 +1,75 @@ +/** + * File: radix_sort.c + * Created Time: 2023-01-18 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */ +int digit(int num, int exp) { + // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す + return (num / exp) % 10; +} + +/* 計数ソート(nums の k 桁目でソート) */ +void countingSortDigit(int nums[], int size, int exp) { + // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 + int *counter = (int *)malloc((sizeof(int) * 10)); + memset(counter, 0, sizeof(int) * 10); // 後続のメモリ解放に備えて 0 で初期化する + // 0~9 の各数字の出現回数を集計する + for (int i = 0; i < size; i++) { + // nums[i] の第 k 位を取得し、d とする + int d = digit(nums[i], exp); + // 数字 d の出現回数を数える + counter[d]++; + } + // 累積和を求め、「出現回数」を「配列インデックス」に変換する + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する + int *res = (int *)malloc(sizeof(int) * size); + for (int i = size - 1; i >= 0; i--) { + int d = digit(nums[i], exp); + int j = counter[d] - 1; // d の配列内インデックス j を取得する + res[j] = nums[i]; // 現在の要素をインデックス j に格納する + counter[d]--; // d の個数を 1 減らす + } + // 結果で元の配列 nums を上書きする + for (int i = 0; i < size; i++) { + nums[i] = res[i]; + } + // メモリを解放する + free(res); + free(counter); +} + +/* 基数ソート */ +void radixSort(int nums[], int size) { + // 最大桁数の判定用に配列の最大要素を取得 + int max = INT32_MIN; + for (int i = 0; i < size; i++) { + if (nums[i] > max) { + max = nums[i]; + } + } + // 下位桁から上位桁の順に走査する + for (int exp = 1; max >= exp; exp *= 10) + // 配列要素の k 桁目に対して計数ソートを行う + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // つまり exp = 10^(k-1) + countingSortDigit(nums, size, exp); +} + +/* Driver Code */ +int main() { + // 基数ソート + int nums[] = {10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996}; + int size = sizeof(nums) / sizeof(int); + radixSort(nums, size); + printf("基数ソート完了後 nums = "); + printArray(nums, size); +} diff --git a/ja/codes/c/chapter_sorting/selection_sort.c b/ja/codes/c/chapter_sorting/selection_sort.c new file mode 100644 index 000000000..ba7454210 --- /dev/null +++ b/ja/codes/c/chapter_sorting/selection_sort.c @@ -0,0 +1,37 @@ +/** + * File: selection_sort.c + * Created Time: 2023-05-31 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 選択ソート */ +void selectionSort(int nums[], int n) { + // 外側ループ:未整列区間は [i, n-1] + for (int i = 0; i < n - 1; i++) { + // 内側のループ:未ソート区間の最小要素を見つける + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) + k = j; // 最小要素のインデックスを記録 + } + // その最小要素を未整列区間の先頭要素と交換する + int temp = nums[i]; + nums[i] = nums[k]; + nums[k] = temp; + } +} + +/* Driver Code */ +int main() { + int nums[] = {4, 1, 3, 1, 5, 2}; + int n = sizeof(nums) / sizeof(nums[0]); + + selectionSort(nums, n); + + printf("選択ソート完了後 nums = "); + printArray(nums, n); + + return 0; +} diff --git a/ja/codes/c/chapter_stack_and_queue/CMakeLists.txt b/ja/codes/c/chapter_stack_and_queue/CMakeLists.txt new file mode 100644 index 000000000..ed3ba840c --- /dev/null +++ b/ja/codes/c/chapter_stack_and_queue/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(array_stack array_stack.c) +add_executable(linkedlist_stack linkedlist_stack.c) +add_executable(array_queue array_queue.c) +add_executable(linkedlist_queue linkedlist_queue.c) +add_executable(array_deque array_deque.c) +add_executable(linkedlist_deque linkedlist_deque.c) diff --git a/ja/codes/c/chapter_stack_and_queue/array_deque.c b/ja/codes/c/chapter_stack_and_queue/array_deque.c new file mode 100644 index 000000000..328267e06 --- /dev/null +++ b/ja/codes/c/chapter_stack_and_queue/array_deque.c @@ -0,0 +1,172 @@ +/** + * File: array_deque.c + * Created Time: 2023-03-13 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 循環配列ベースの両端キュー */ +typedef struct { + int *nums; // キュー要素を格納する配列 + int front; // 先頭ポインタ。先頭要素を指す + int queSize; // 末尾ポインタ。キューの末尾 + 1 を指す + int queCapacity; // キューの容量 +} ArrayDeque; + +/* コンストラクタ */ +ArrayDeque *newArrayDeque(int capacity) { + ArrayDeque *deque = (ArrayDeque *)malloc(sizeof(ArrayDeque)); + // 配列を初期化 + deque->queCapacity = capacity; + deque->nums = (int *)malloc(sizeof(int) * deque->queCapacity); + deque->front = deque->queSize = 0; + return deque; +} + +/* デストラクタ */ +void delArrayDeque(ArrayDeque *deque) { + free(deque->nums); + free(deque); +} + +/* 両端キューの容量を取得 */ +int capacity(ArrayDeque *deque) { + return deque->queCapacity; +} + +/* 両端キューの長さを取得 */ +int size(ArrayDeque *deque) { + return deque->queSize; +} + +/* 両端キューが空かどうかを判定 */ +bool empty(ArrayDeque *deque) { + return deque->queSize == 0; +} + +/* 循環配列のインデックスを計算 */ +int dequeIndex(ArrayDeque *deque, int i) { + // 剰余演算により配列の先頭と末尾をつなげる + // i が配列の末尾を越えたら先頭に戻る + // i が配列の先頭を越えたら末尾に戻る + return ((i + capacity(deque)) % capacity(deque)); +} + +/* キュー先頭にエンキュー */ +void pushFirst(ArrayDeque *deque, int num) { + if (deque->queSize == capacity(deque)) { + printf("両端キューがいっぱいです\r\n"); + return; + } + // 先頭ポインタを左に 1 つ移動する + // 剰余演算により front が配列の先頭を越えたあと末尾に戻るようにする + deque->front = dequeIndex(deque, deque->front - 1); + // num をキューの先頭に追加 + deque->nums[deque->front] = num; + deque->queSize++; +} + +/* キュー末尾にエンキュー */ +void pushLast(ArrayDeque *deque, int num) { + if (deque->queSize == capacity(deque)) { + printf("両端キューがいっぱいです\r\n"); + return; + } + // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す + int rear = dequeIndex(deque, deque->front + deque->queSize); + // num をキュー末尾に追加 + deque->nums[rear] = num; + deque->queSize++; +} + +/* キュー先頭の要素にアクセス */ +int peekFirst(ArrayDeque *deque) { + // アクセス例外:双方向キューが空です + assert(empty(deque) == 0); + return deque->nums[deque->front]; +} + +/* キュー末尾の要素にアクセス */ +int peekLast(ArrayDeque *deque) { + // アクセス例外:双方向キューが空です + assert(empty(deque) == 0); + int last = dequeIndex(deque, deque->front + deque->queSize - 1); + return deque->nums[last]; +} + +/* キュー先頭からデキュー */ +int popFirst(ArrayDeque *deque) { + int num = peekFirst(deque); + // 先頭ポインタを 1 つ後ろへ進める + deque->front = dequeIndex(deque, deque->front + 1); + deque->queSize--; + return num; +} + +/* キュー末尾からデキュー */ +int popLast(ArrayDeque *deque) { + int num = peekLast(deque); + deque->queSize--; + return num; +} + +/* 出力用の配列を返す */ +int *toArray(ArrayDeque *deque, int *queSize) { + *queSize = deque->queSize; + int *res = (int *)calloc(deque->queSize, sizeof(int)); + int j = deque->front; + for (int i = 0; i < deque->queSize; i++) { + res[i] = deque->nums[j % deque->queCapacity]; + j++; + } + return res; +} + +/* Driver Code */ +int main() { + /* キューを初期化 */ + int capacity = 10; + int queSize; + ArrayDeque *deque = newArrayDeque(capacity); + pushLast(deque, 3); + pushLast(deque, 2); + pushLast(deque, 5); + printf("両端キュー deque = "); + printArray(toArray(deque, &queSize), queSize); + + /* 要素にアクセス */ + int peekFirstNum = peekFirst(deque); + printf("先頭要素 peekFirst = %d\r\n", peekFirstNum); + int peekLastNum = peekLast(deque); + printf("末尾要素 peekLast = %d\r\n", peekLastNum); + + /* 要素をエンキュー */ + pushLast(deque, 4); + printf("要素 4 を末尾に追加後 deque = "); + printArray(toArray(deque, &queSize), queSize); + pushFirst(deque, 1); + printf("要素 1 を先頭に追加後 deque = "); + printArray(toArray(deque, &queSize), queSize); + + /* 要素をデキュー */ + int popLastNum = popLast(deque); + printf("末尾から取り出した要素 = %d ,末尾から取り出した後 deque= ", popLastNum); + printArray(toArray(deque, &queSize), queSize); + int popFirstNum = popFirst(deque); + printf("先頭から取り出した要素 = %d ,先頭から取り出した後 deque= ", popFirstNum); + printArray(toArray(deque, &queSize), queSize); + + /* キューの長さを取得 */ + int dequeSize = size(deque); + printf("両端キューの長さ size = %d\r\n", dequeSize); + + /* キューが空かどうかを判定 */ + bool isEmpty = empty(deque); + printf("キューが空かどうか = %s\r\n", isEmpty ? "true" : "false"); + + // メモリを解放する + delArrayDeque(deque); + + return 0; +} \ No newline at end of file diff --git a/ja/codes/c/chapter_stack_and_queue/array_queue.c b/ja/codes/c/chapter_stack_and_queue/array_queue.c new file mode 100644 index 000000000..f3ca29124 --- /dev/null +++ b/ja/codes/c/chapter_stack_and_queue/array_queue.c @@ -0,0 +1,134 @@ +/** + * File: array_queue.c + * Created Time: 2023-01-28 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 循環配列ベースのキュー */ +typedef struct { + int *nums; // キュー要素を格納する配列 + int front; // 先頭ポインタ。先頭要素を指す + int queSize; // 末尾ポインタ。キューの末尾 + 1 を指す + int queCapacity; // キューの容量 +} ArrayQueue; + +/* コンストラクタ */ +ArrayQueue *newArrayQueue(int capacity) { + ArrayQueue *queue = (ArrayQueue *)malloc(sizeof(ArrayQueue)); + // 配列を初期化 + queue->queCapacity = capacity; + queue->nums = (int *)malloc(sizeof(int) * queue->queCapacity); + queue->front = queue->queSize = 0; + return queue; +} + +/* デストラクタ */ +void delArrayQueue(ArrayQueue *queue) { + free(queue->nums); + free(queue); +} + +/* キューの容量を取得 */ +int capacity(ArrayQueue *queue) { + return queue->queCapacity; +} + +/* キューの長さを取得 */ +int size(ArrayQueue *queue) { + return queue->queSize; +} + +/* キューが空かどうかを判定 */ +bool empty(ArrayQueue *queue) { + return queue->queSize == 0; +} + +/* キュー先頭の要素にアクセス */ +int peek(ArrayQueue *queue) { + assert(size(queue) != 0); + return queue->nums[queue->front]; +} + +/* エンキュー */ +void push(ArrayQueue *queue, int num) { + if (size(queue) == capacity(queue)) { + printf("キューは満杯です\r\n"); + return; + } + // 末尾ポインタを計算し、末尾インデックス + 1 を指す + // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする + int rear = (queue->front + queue->queSize) % queue->queCapacity; + // num をキュー末尾に追加 + queue->nums[rear] = num; + queue->queSize++; +} + +/* デキュー */ +int pop(ArrayQueue *queue) { + int num = peek(queue); + // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す + queue->front = (queue->front + 1) % queue->queCapacity; + queue->queSize--; + return num; +} + +/* 出力用の配列を返す */ +int *toArray(ArrayQueue *queue, int *queSize) { + *queSize = queue->queSize; + int *res = (int *)calloc(queue->queSize, sizeof(int)); + int j = queue->front; + for (int i = 0; i < queue->queSize; i++) { + res[i] = queue->nums[j % queue->queCapacity]; + j++; + } + return res; +} + +/* Driver Code */ +int main() { + /* キューを初期化 */ + int capacity = 10; + int queSize; + ArrayQueue *queue = newArrayQueue(capacity); + + /* 要素をエンキュー */ + push(queue, 1); + push(queue, 3); + push(queue, 2); + push(queue, 5); + push(queue, 4); + printf("キュー queue = "); + printArray(toArray(queue, &queSize), queSize); + + /* キュー先頭の要素にアクセス */ + int peekNum = peek(queue); + printf("先頭要素 peek = %d\r\n", peekNum); + + /* 要素をデキュー */ + peekNum = pop(queue); + printf("デキューした要素 pop = %d ,デキュー後 queue = ", peekNum); + printArray(toArray(queue, &queSize), queSize); + + /* キューの長さを取得 */ + int queueSize = size(queue); + printf("キューの長さ size = %d\r\n", queueSize); + + /* キューが空かどうかを判定 */ + bool isEmpty = empty(queue); + printf("キューが空かどうか = %s\r\n", isEmpty ? "true" : "false"); + + /* 循環配列をテストする */ + for (int i = 0; i < 10; i++) { + push(queue, i); + pop(queue); + printf("第 %d 回のエンキュー + デキュー後 queue = ", i); + printArray(toArray(queue, &queSize), queSize); + } + + // メモリを解放する + delArrayQueue(queue); + + return 0; +} \ No newline at end of file diff --git a/ja/codes/c/chapter_stack_and_queue/array_stack.c b/ja/codes/c/chapter_stack_and_queue/array_stack.c new file mode 100644 index 000000000..0583de7c5 --- /dev/null +++ b/ja/codes/c/chapter_stack_and_queue/array_stack.c @@ -0,0 +1,103 @@ +/** + * File: array_stack.c + * Created Time: 2023-01-12 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 5000 + +/* 配列ベースのスタック */ +typedef struct { + int *data; + int size; +} ArrayStack; + +/* コンストラクタ */ +ArrayStack *newArrayStack() { + ArrayStack *stack = malloc(sizeof(ArrayStack)); + // 大きめの容量で初期化し、拡張を避ける + stack->data = malloc(sizeof(int) * MAX_SIZE); + stack->size = 0; + return stack; +} + +/* デストラクタ */ +void delArrayStack(ArrayStack *stack) { + free(stack->data); + free(stack); +} + +/* スタックの長さを取得 */ +int size(ArrayStack *stack) { + return stack->size; +} + +/* スタックが空かどうかを判定 */ +bool isEmpty(ArrayStack *stack) { + return stack->size == 0; +} + +/* プッシュ */ +void push(ArrayStack *stack, int num) { + if (stack->size == MAX_SIZE) { + printf("スタックは満杯です\n"); + return; + } + stack->data[stack->size] = num; + stack->size++; +} + +/* スタックトップの要素にアクセス */ +int peek(ArrayStack *stack) { + if (stack->size == 0) { + printf("スタックは空です\n"); + return INT_MAX; + } + return stack->data[stack->size - 1]; +} + +/* ポップ */ +int pop(ArrayStack *stack) { + int val = peek(stack); + stack->size--; + return val; +} + +/* Driver Code */ +int main() { + /* スタックを初期化 */ + ArrayStack *stack = newArrayStack(); + + /* 要素をプッシュ */ + push(stack, 1); + push(stack, 3); + push(stack, 2); + push(stack, 5); + push(stack, 4); + printf("スタック stack = "); + printArray(stack->data, stack->size); + + /* スタックトップの要素にアクセス */ + int val = peek(stack); + printf("先頭要素 top = %d\n", val); + + /* 要素をポップ */ + val = pop(stack); + printf("ポップした要素 pop = %d ,ポップ後 stack = ", val); + printArray(stack->data, stack->size); + + /* スタックの長さを取得 */ + int size = stack->size; + printf("スタックの長さ size = %d\n", size); + + /* 空かどうかを判定 */ + bool empty = isEmpty(stack); + printf("スタックが空かどうか = %s\n", empty ? "true" : "false"); + + // メモリを解放する + delArrayStack(stack); + + return 0; +} diff --git a/ja/codes/c/chapter_stack_and_queue/linkedlist_deque.c b/ja/codes/c/chapter_stack_and_queue/linkedlist_deque.c new file mode 100644 index 000000000..55aaa152b --- /dev/null +++ b/ja/codes/c/chapter_stack_and_queue/linkedlist_deque.c @@ -0,0 +1,212 @@ +/** + * File: linkedlist_deque.c + * Created Time: 2023-03-13 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 双方向連結リストノード */ +typedef struct DoublyListNode { + int val; // ノード値 + struct DoublyListNode *next; // 後続ノード + struct DoublyListNode *prev; // 前駆ノード +} DoublyListNode; + +/* コンストラクタ */ +DoublyListNode *newDoublyListNode(int num) { + DoublyListNode *new = (DoublyListNode *)malloc(sizeof(DoublyListNode)); + new->val = num; + new->next = NULL; + new->prev = NULL; + return new; +} + +/* デストラクタ */ +void delDoublyListNode(DoublyListNode *node) { + free(node); +} + +/* 双方向連結リストベースの両端キュー */ +typedef struct { + DoublyListNode *front, *rear; // 先頭ノード front、末尾ノード rear + int queSize; // 両端キューの長さ +} LinkedListDeque; + +/* コンストラクタ */ +LinkedListDeque *newLinkedListDeque() { + LinkedListDeque *deque = (LinkedListDeque *)malloc(sizeof(LinkedListDeque)); + deque->front = NULL; + deque->rear = NULL; + deque->queSize = 0; + return deque; +} + +/* デストラクタ */ +void delLinkedListdeque(LinkedListDeque *deque) { + // すべてのノードを解放 + for (int i = 0; i < deque->queSize && deque->front != NULL; i++) { + DoublyListNode *tmp = deque->front; + deque->front = deque->front->next; + free(tmp); + } + // deque 構造体を解放する + free(deque); +} + +/* キューの長さを取得 */ +int size(LinkedListDeque *deque) { + return deque->queSize; +} + +/* キューが空かどうかを判定 */ +bool empty(LinkedListDeque *deque) { + return (size(deque) == 0); +} + +/* エンキュー */ +void push(LinkedListDeque *deque, int num, bool isFront) { + DoublyListNode *node = newDoublyListNode(num); + // 連結リストが空なら、`front` と `rear` の両方を `node` に向ける + if (empty(deque)) { + deque->front = deque->rear = node; + } + // 先頭へのエンキュー操作 + else if (isFront) { + // node を連結リストの先頭に追加 + deque->front->prev = node; + node->next = deque->front; + deque->front = node; // 先頭ノードを更新する + } + // 末尾へのエンキュー操作 + else { + // node を連結リストの末尾に追加 + deque->rear->next = node; + node->prev = deque->rear; + deque->rear = node; + } + deque->queSize++; // キューの長さを更新 +} + +/* キュー先頭にエンキュー */ +void pushFirst(LinkedListDeque *deque, int num) { + push(deque, num, true); +} + +/* キュー末尾にエンキュー */ +void pushLast(LinkedListDeque *deque, int num) { + push(deque, num, false); +} + +/* キュー先頭の要素にアクセス */ +int peekFirst(LinkedListDeque *deque) { + assert(size(deque) && deque->front); + return deque->front->val; +} + +/* キュー末尾の要素にアクセス */ +int peekLast(LinkedListDeque *deque) { + assert(size(deque) && deque->rear); + return deque->rear->val; +} + +/* デキュー */ +int pop(LinkedListDeque *deque, bool isFront) { + if (empty(deque)) + return -1; + int val; + // キュー先頭からの取り出し + if (isFront) { + val = peekFirst(deque); // 先頭ノードの値を一時保存 + DoublyListNode *fNext = deque->front->next; + if (fNext) { + fNext->prev = NULL; + deque->front->next = NULL; + } + delDoublyListNode(deque->front); + deque->front = fNext; // 先頭ノードを更新する + } + // キュー末尾からの取り出し + else { + val = peekLast(deque); // 末尾ノードの値を一時保存 + DoublyListNode *rPrev = deque->rear->prev; + if (rPrev) { + rPrev->next = NULL; + deque->rear->prev = NULL; + } + delDoublyListNode(deque->rear); + deque->rear = rPrev; // 末尾ノードを更新する + } + deque->queSize--; // キューの長さを更新 + return val; +} + +/* キュー先頭からデキュー */ +int popFirst(LinkedListDeque *deque) { + return pop(deque, true); +} + +/* キュー末尾からデキュー */ +int popLast(LinkedListDeque *deque) { + return pop(deque, false); +} + +/* キューを出力する */ +void printLinkedListDeque(LinkedListDeque *deque) { + int *arr = malloc(sizeof(int) * deque->queSize); + // 連結リスト内のデータを配列にコピー + int i; + DoublyListNode *node; + for (i = 0, node = deque->front; i < deque->queSize; i++) { + arr[i] = node->val; + node = node->next; + } + printArray(arr, deque->queSize); + free(arr); +} + +/* Driver Code */ +int main() { + /* 両端キューを初期化 */ + LinkedListDeque *deque = newLinkedListDeque(); + pushLast(deque, 3); + pushLast(deque, 2); + pushLast(deque, 5); + printf("両端キュー deque = "); + printLinkedListDeque(deque); + + /* 要素にアクセス */ + int peekFirstNum = peekFirst(deque); + printf("先頭要素 peekFirst = %d\r\n", peekFirstNum); + int peekLastNum = peekLast(deque); + printf("先頭要素 peekLast = %d\r\n", peekLastNum); + + /* 要素をエンキュー */ + pushLast(deque, 4); + printf("要素 4 を末尾に追加した後 deque ="); + printLinkedListDeque(deque); + pushFirst(deque, 1); + printf("要素 1 を先頭に追加した後 deque ="); + printLinkedListDeque(deque); + + /* 要素をデキュー */ + int popLastNum = popLast(deque); + printf("末尾から取り出した要素 popLast = %d ,末尾から取り出した後 deque = ", popLastNum); + printLinkedListDeque(deque); + int popFirstNum = popFirst(deque); + printf("先頭から取り出した要素 popFirst = %d ,先頭から取り出した後 deque = ", popFirstNum); + printLinkedListDeque(deque); + + /* キューの長さを取得 */ + int dequeSize = size(deque); + printf("両端キューの長さ size = %d\r\n", dequeSize); + + /* キューが空かどうかを判定 */ + bool isEmpty = empty(deque); + printf("両端キューが空かどうか = %s\r\n", isEmpty ? "true" : "false"); + + // メモリを解放する + delLinkedListdeque(deque); + + return 0; +} diff --git a/ja/codes/c/chapter_stack_and_queue/linkedlist_queue.c b/ja/codes/c/chapter_stack_and_queue/linkedlist_queue.c new file mode 100644 index 000000000..a9fae5fdb --- /dev/null +++ b/ja/codes/c/chapter_stack_and_queue/linkedlist_queue.c @@ -0,0 +1,128 @@ +/** + * File: linkedlist_queue.c + * Created Time: 2023-03-13 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 連結リストベースのキュー */ +typedef struct { + ListNode *front, *rear; + int queSize; +} LinkedListQueue; + +/* コンストラクタ */ +LinkedListQueue *newLinkedListQueue() { + LinkedListQueue *queue = (LinkedListQueue *)malloc(sizeof(LinkedListQueue)); + queue->front = NULL; + queue->rear = NULL; + queue->queSize = 0; + return queue; +} + +/* デストラクタ */ +void delLinkedListQueue(LinkedListQueue *queue) { + // すべてのノードを解放 + while (queue->front != NULL) { + ListNode *tmp = queue->front; + queue->front = queue->front->next; + free(tmp); + } + // queue 構造体を解放する + free(queue); +} + +/* キューの長さを取得 */ +int size(LinkedListQueue *queue) { + return queue->queSize; +} + +/* キューが空かどうかを判定 */ +bool empty(LinkedListQueue *queue) { + return (size(queue) == 0); +} + +/* エンキュー */ +void push(LinkedListQueue *queue, int num) { + // 末尾ノードに node を追加 + ListNode *node = newListNode(num); + // キューが空なら、先頭・末尾ノードをともにそのノードに設定 + if (queue->front == NULL) { + queue->front = node; + queue->rear = node; + } + // キューが空でなければ、そのノードを末尾ノードの後ろに追加 + else { + queue->rear->next = node; + queue->rear = node; + } + queue->queSize++; +} + +/* キュー先頭の要素にアクセス */ +int peek(LinkedListQueue *queue) { + assert(size(queue) && queue->front); + return queue->front->val; +} + +/* デキュー */ +int pop(LinkedListQueue *queue) { + int num = peek(queue); + ListNode *tmp = queue->front; + queue->front = queue->front->next; + free(tmp); + queue->queSize--; + return num; +} + +/* キューを出力する */ +void printLinkedListQueue(LinkedListQueue *queue) { + int *arr = malloc(sizeof(int) * queue->queSize); + // 連結リスト内のデータを配列にコピー + int i; + ListNode *node; + for (i = 0, node = queue->front; i < queue->queSize; i++) { + arr[i] = node->val; + node = node->next; + } + printArray(arr, queue->queSize); + free(arr); +} + +/* Driver Code */ +int main() { + /* キューを初期化 */ + LinkedListQueue *queue = newLinkedListQueue(); + + /* 要素をエンキュー */ + push(queue, 1); + push(queue, 3); + push(queue, 2); + push(queue, 5); + push(queue, 4); + printf("キュー queue = "); + printLinkedListQueue(queue); + + /* キュー先頭の要素にアクセス */ + int peekNum = peek(queue); + printf("先頭要素 peek = %d\r\n", peekNum); + + /* 要素をデキュー */ + peekNum = pop(queue); + printf("デキューした要素 pop = %d ,デキュー後 queue = ", peekNum); + printLinkedListQueue(queue); + + /* キューの長さを取得 */ + int queueSize = size(queue); + printf("キューの長さ size = %d\r\n", queueSize); + + /* キューが空かどうかを判定 */ + bool isEmpty = empty(queue); + printf("キューが空かどうか = %s\r\n", isEmpty ? "true" : "false"); + + // メモリを解放する + delLinkedListQueue(queue); + + return 0; +} diff --git a/ja/codes/c/chapter_stack_and_queue/linkedlist_stack.c b/ja/codes/c/chapter_stack_and_queue/linkedlist_stack.c new file mode 100644 index 000000000..b42a84f05 --- /dev/null +++ b/ja/codes/c/chapter_stack_and_queue/linkedlist_stack.c @@ -0,0 +1,107 @@ +/** + * File: linkedlist_stack.c + * Created Time: 2023-01-12 + * Author: Zero (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 連結リストベースのスタック */ +typedef struct { + ListNode *top; // 先頭ノードをスタックトップとする + int size; // スタックの長さ +} LinkedListStack; + +/* コンストラクタ */ +LinkedListStack *newLinkedListStack() { + LinkedListStack *s = malloc(sizeof(LinkedListStack)); + s->top = NULL; + s->size = 0; + return s; +} + +/* デストラクタ */ +void delLinkedListStack(LinkedListStack *s) { + while (s->top) { + ListNode *n = s->top->next; + free(s->top); + s->top = n; + } + free(s); +} + +/* スタックの長さを取得 */ +int size(LinkedListStack *s) { + return s->size; +} + +/* スタックが空かどうかを判定 */ +bool isEmpty(LinkedListStack *s) { + return size(s) == 0; +} + +/* プッシュ */ +void push(LinkedListStack *s, int num) { + ListNode *node = (ListNode *)malloc(sizeof(ListNode)); + node->next = s->top; // 新しく追加したノードのポインタフィールドを更新 + node->val = num; // 新しく追加したノードのデータフィールドを更新 + s->top = node; // スタックトップを更新 + s->size++; // スタックサイズを更新 +} + +/* スタックトップの要素にアクセス */ +int peek(LinkedListStack *s) { + if (s->size == 0) { + printf("スタックは空です\n"); + return INT_MAX; + } + return s->top->val; +} + +/* ポップ */ +int pop(LinkedListStack *s) { + int val = peek(s); + ListNode *tmp = s->top; + s->top = s->top->next; + // メモリを解放する + free(tmp); + s->size--; + return val; +} + +/* Driver Code */ +int main() { + /* スタックを初期化 */ + LinkedListStack *stack = newLinkedListStack(); + + /* 要素をプッシュ */ + push(stack, 1); + push(stack, 3); + push(stack, 2); + push(stack, 5); + push(stack, 4); + + printf("スタック stack = "); + printLinkedList(stack->top); + + /* スタックトップの要素にアクセス */ + int val = peek(stack); + printf("スタックトップ要素 top = %d\r\n", val); + + /* 要素をポップ */ + val = pop(stack); + printf("ポップした要素 pop = %d, ポップ後 stack = ", val); + printLinkedList(stack->top); + + /* スタックの長さを取得 */ + printf("スタックの長さ size = %d\n", size(stack)); + + /* 空かどうかを判定 */ + bool empty = isEmpty(stack); + printf("スタックが空かどうか = %s\n", empty ? "true" : "false"); + + // メモリを解放する + delLinkedListStack(stack); + + return 0; +} diff --git a/ja/codes/c/chapter_tree/CMakeLists.txt b/ja/codes/c/chapter_tree/CMakeLists.txt new file mode 100644 index 000000000..9b4e825ff --- /dev/null +++ b/ja/codes/c/chapter_tree/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(avl_tree avl_tree.c) +add_executable(binary_tree binary_tree.c) +add_executable(binary_tree_bfs binary_tree_bfs.c) +add_executable(binary_tree_dfs binary_tree_dfs.c) +add_executable(binary_search_tree binary_search_tree.c) +add_executable(array_binary_tree array_binary_tree.c) diff --git a/ja/codes/c/chapter_tree/array_binary_tree.c b/ja/codes/c/chapter_tree/array_binary_tree.c new file mode 100644 index 000000000..cf60b5344 --- /dev/null +++ b/ja/codes/c/chapter_tree/array_binary_tree.c @@ -0,0 +1,166 @@ +/** + * File: array_binary_tree.c + * Created Time: 2023-07-29 + * Author: Gonglja (glj0@outlook.com) + */ + +#include "../utils/common.h" + +/* 配列表現による二分木の構造体 */ +typedef struct { + int *tree; + int size; +} ArrayBinaryTree; + +/* コンストラクタ */ +ArrayBinaryTree *newArrayBinaryTree(int *arr, int arrSize) { + ArrayBinaryTree *abt = (ArrayBinaryTree *)malloc(sizeof(ArrayBinaryTree)); + abt->tree = malloc(sizeof(int) * arrSize); + memcpy(abt->tree, arr, sizeof(int) * arrSize); + abt->size = arrSize; + return abt; +} + +/* デストラクタ */ +void delArrayBinaryTree(ArrayBinaryTree *abt) { + free(abt->tree); + free(abt); +} + +/* リスト容量 */ +int size(ArrayBinaryTree *abt) { + return abt->size; +} + +/* インデックス i のノードの値を取得 */ +int val(ArrayBinaryTree *abt, int i) { + // インデックスが範囲外なら、空きを表す INT_MAX を返す + if (i < 0 || i >= size(abt)) + return INT_MAX; + return abt->tree[i]; +} + +/* インデックス i のノードの左子ノードのインデックスを取得 */ +int left(int i) { + return 2 * i + 1; +} + +/* インデックス i のノードの右子ノードのインデックスを取得 */ +int right(int i) { + return 2 * i + 2; +} + +/* インデックス i のノードの親ノードのインデックスを取得 */ +int parent(int i) { + return (i - 1) / 2; +} + +/* レベル順走査 */ +int *levelOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + // 配列を直接走査する + for (int i = 0; i < size(abt); i++) { + if (val(abt, i) != INT_MAX) + res[index++] = val(abt, i); + } + *returnSize = index; + return res; +} + +/* 深さ優先探索 */ +void dfs(ArrayBinaryTree *abt, int i, char *order, int *res, int *index) { + // 空きスロットなら返す + if (val(abt, i) == INT_MAX) + return; + // 先行順走査 + if (strcmp(order, "pre") == 0) + res[(*index)++] = val(abt, i); + dfs(abt, left(i), order, res, index); + // 中順走査 + if (strcmp(order, "in") == 0) + res[(*index)++] = val(abt, i); + dfs(abt, right(i), order, res, index); + // 後順走査 + if (strcmp(order, "post") == 0) + res[(*index)++] = val(abt, i); +} + +/* 先行順走査 */ +int *preOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + dfs(abt, 0, "pre", res, &index); + *returnSize = index; + return res; +} + +/* 中順走査 */ +int *inOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + dfs(abt, 0, "in", res, &index); + *returnSize = index; + return res; +} + +/* 後順走査 */ +int *postOrder(ArrayBinaryTree *abt, int *returnSize) { + int *res = (int *)malloc(sizeof(int) * size(abt)); + int index = 0; + dfs(abt, 0, "post", res, &index); + *returnSize = index; + return res; +} + +/* Driver Code */ +int main() { + // 二分木を初期化する + // 空き位置 NULL は INT_MAX で表す + int arr[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; + int arrSize = sizeof(arr) / sizeof(arr[0]); + TreeNode *root = arrayToTree(arr, arrSize); + printf("\n二分木を初期化\n"); + printf("二分木の配列表現:\n"); + printArray(arr, arrSize); + printf("二分木の連結リスト表現:\n"); + printTree(root); + + ArrayBinaryTree *abt = newArrayBinaryTree(arr, arrSize); + + // ノードにアクセス + int i = 1; + int l = left(i), r = right(i), p = parent(i); + printf("\n現在のノードのインデックスは %d、値は %d\n", i, val(abt, i)); + printf("左の子ノードのインデックスは %d、値は %d\n", l, l < arrSize ? val(abt, l) : INT_MAX); + printf("右の子ノードのインデックスは %d、値は %d\n", r, r < arrSize ? val(abt, r) : INT_MAX); + printf("親ノードのインデックスは %d、値は %d\n", p, p < arrSize ? val(abt, p) : INT_MAX); + + // 木を走査 + int returnSize; + int *res; + + res = levelOrder(abt, &returnSize); + printf("\nレベル順走査: "); + printArray(res, returnSize); + free(res); + + res = preOrder(abt, &returnSize); + printf("前順走査: "); + printArray(res, returnSize); + free(res); + + res = inOrder(abt, &returnSize); + printf("中順走査: "); + printArray(res, returnSize); + free(res); + + res = postOrder(abt, &returnSize); + printf("後順走査: "); + printArray(res, returnSize); + free(res); + + // メモリを解放する + delArrayBinaryTree(abt); + return 0; +} diff --git a/ja/codes/c/chapter_tree/avl_tree.c b/ja/codes/c/chapter_tree/avl_tree.c new file mode 100644 index 000000000..3251350a4 --- /dev/null +++ b/ja/codes/c/chapter_tree/avl_tree.c @@ -0,0 +1,259 @@ +/** + * File: avl_tree.c + * Created Time: 2023-01-15 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* AVL 木構造体 */ +typedef struct { + TreeNode *root; +} AVLTree; + +/* コンストラクタ */ +AVLTree *newAVLTree() { + AVLTree *tree = (AVLTree *)malloc(sizeof(AVLTree)); + tree->root = NULL; + return tree; +} + +/* デストラクタ */ +void delAVLTree(AVLTree *tree) { + freeMemoryTree(tree->root); + free(tree); +} + +/* ノードの高さを取得 */ +int height(TreeNode *node) { + // 空ノードの高さは -1、葉ノードの高さは 0 + if (node != NULL) { + return node->height; + } + return -1; +} + +/* ノードの高さを更新する */ +void updateHeight(TreeNode *node) { + int lh = height(node->left); + int rh = height(node->right); + // ノードの高さは最も高い部分木の高さ + 1 に等しい + if (lh > rh) { + node->height = lh + 1; + } else { + node->height = rh + 1; + } +} + +/* 平衡係数を取得 */ +int balanceFactor(TreeNode *node) { + // 空ノードの平衡係数は 0 + if (node == NULL) { + return 0; + } + // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ + return height(node->left) - height(node->right); +} + +/* 右回転 */ +TreeNode *rightRotate(TreeNode *node) { + TreeNode *child, *grandChild; + child = node->left; + grandChild = child->right; + // child を支点として node を右回転させる + child->right = node; + node->left = grandChild; + // ノードの高さを更新する + updateHeight(node); + updateHeight(child); + // 回転後の部分木の根ノードを返す + return child; +} + +/* 左回転 */ +TreeNode *leftRotate(TreeNode *node) { + TreeNode *child, *grandChild; + child = node->right; + grandChild = child->left; + // child を支点として node を左回転させる + child->left = node; + node->right = grandChild; + // ノードの高さを更新する + updateHeight(node); + updateHeight(child); + // 回転後の部分木の根ノードを返す + return child; +} + +/* 回転操作を行い、この部分木の平衡を回復する */ +TreeNode *rotate(TreeNode *node) { + // ノード node の平衡係数を取得 + int bf = balanceFactor(node); + // 左に偏った木 + if (bf > 1) { + if (balanceFactor(node->left) >= 0) { + // 右回転 + return rightRotate(node); + } else { + // 左回転してから右回転 + node->left = leftRotate(node->left); + return rightRotate(node); + } + } + // 右に偏った木 + if (bf < -1) { + if (balanceFactor(node->right) <= 0) { + // 左回転 + return leftRotate(node); + } else { + // 右回転してから左回転 + node->right = rightRotate(node->right); + return leftRotate(node); + } + } + // 平衡木なので回転不要、そのまま返す + return node; +} + +/* ノードを再帰的に挿入する(補助関数) */ +TreeNode *insertHelper(TreeNode *node, int val) { + if (node == NULL) { + return newTreeNode(val); + } + /* 1. 挿入位置を探索してノードを挿入 */ + if (val < node->val) { + node->left = insertHelper(node->left, val); + } else if (val > node->val) { + node->right = insertHelper(node->right, val); + } else { + // 重複ノードは挿入せず、そのまま返す + return node; + } + // ノードの高さを更新する + updateHeight(node); + /* 2. 回転操作を行い、部分木の平衡を回復する */ + node = rotate(node); + // 部分木の根ノードを返す + return node; +} + +/* ノードを挿入 */ +void insert(AVLTree *tree, int val) { + tree->root = insertHelper(tree->root, val); +} + +/* ノードを再帰的に削除する(補助関数) */ +TreeNode *removeHelper(TreeNode *node, int val) { + TreeNode *child, *grandChild; + if (node == NULL) { + return NULL; + } + /* 1. ノードを探索して削除 */ + if (val < node->val) { + node->left = removeHelper(node->left, val); + } else if (val > node->val) { + node->right = removeHelper(node->right, val); + } else { + if (node->left == NULL || node->right == NULL) { + child = node->left; + if (node->right != NULL) { + child = node->right; + } + // 子ノード数 = 0 の場合、node をそのまま削除して返す + if (child == NULL) { + return NULL; + } else { + // 子ノード数 = 1 の場合、node をそのまま削除する + node = child; + } + } else { + // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える + TreeNode *temp = node->right; + while (temp->left != NULL) { + temp = temp->left; + } + int tempVal = temp->val; + node->right = removeHelper(node->right, temp->val); + node->val = tempVal; + } + } + // ノードの高さを更新する + updateHeight(node); + /* 2. 回転操作を行い、部分木の平衡を回復する */ + node = rotate(node); + // 部分木の根ノードを返す + return node; +} + +/* ノードを削除 */ +// stdio.h を導入しているため、ここでは remove 識別子を使えない +void removeItem(AVLTree *tree, int val) { + TreeNode *root = removeHelper(tree->root, val); +} + +/* ノードを探索 */ +TreeNode *search(AVLTree *tree, int val) { + TreeNode *cur = tree->root; + // ループで探索し、葉ノードを越えたら抜ける + while (cur != NULL) { + if (cur->val < val) { + // 目標ノードは cur の右部分木にある + cur = cur->right; + } else if (cur->val > val) { + // 目標ノードは cur の左部分木にある + cur = cur->left; + } else { + // 目標ノードが見つかったらループを抜ける + break; + } + } + // 目標ノードが見つかったらループを抜ける + return cur; +} + +void testInsert(AVLTree *tree, int val) { + insert(tree, val); + printf("\nノード %d を挿入した後、AVL 木は \n", val); + printTree(tree->root); +} + +void testRemove(AVLTree *tree, int val) { + removeItem(tree, val); + printf("\nノード %d を削除した後、AVL 木は \n", val); + printTree(tree->root); +} + +/* Driver Code */ +int main() { + /* 空の AVL 木を初期化する */ + AVLTree *tree = (AVLTree *)newAVLTree(); + /* ノードを挿入 */ + // ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい + testInsert(tree, 1); + testInsert(tree, 2); + testInsert(tree, 3); + testInsert(tree, 4); + testInsert(tree, 5); + testInsert(tree, 8); + testInsert(tree, 7); + testInsert(tree, 9); + testInsert(tree, 10); + testInsert(tree, 6); + + /* 重複ノードを挿入する */ + testInsert(tree, 7); + + /* ノードを削除 */ + // ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい + testRemove(tree, 8); // 次数 0 のノードを削除する + testRemove(tree, 5); // 次数 1 のノードを削除する + testRemove(tree, 4); // 次数 2 のノードを削除する + + /* ノードを検索 */ + TreeNode *node = search(tree, 7); + printf("\n見つかったノードオブジェクトのノード値 = %d \n", node->val); + + // メモリを解放する + delAVLTree(tree); + return 0; +} diff --git a/ja/codes/c/chapter_tree/binary_search_tree.c b/ja/codes/c/chapter_tree/binary_search_tree.c new file mode 100644 index 000000000..4c01ab41e --- /dev/null +++ b/ja/codes/c/chapter_tree/binary_search_tree.c @@ -0,0 +1,171 @@ +/** + * File: binary_search_tree.c + * Created Time: 2023-01-11 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* 二分探索木構造体 */ +typedef struct { + TreeNode *root; +} BinarySearchTree; + +/* コンストラクタ */ +BinarySearchTree *newBinarySearchTree() { + // 空の木を初期化する + BinarySearchTree *bst = (BinarySearchTree *)malloc(sizeof(BinarySearchTree)); + bst->root = NULL; + return bst; +} + +/* デストラクタ */ +void delBinarySearchTree(BinarySearchTree *bst) { + freeMemoryTree(bst->root); + free(bst); +} + +/* 二分木の根ノードを取得 */ +TreeNode *getRoot(BinarySearchTree *bst) { + return bst->root; +} + +/* ノードを探索 */ +TreeNode *search(BinarySearchTree *bst, int num) { + TreeNode *cur = bst->root; + // ループで探索し、葉ノードを越えたら抜ける + while (cur != NULL) { + if (cur->val < num) { + // 目標ノードは cur の右部分木にある + cur = cur->right; + } else if (cur->val > num) { + // 目標ノードは cur の左部分木にある + cur = cur->left; + } else { + // 目標ノードが見つかったらループを抜ける + break; + } + } + // 目標ノードを返す + return cur; +} + +/* ノードを挿入 */ +void insert(BinarySearchTree *bst, int num) { + // 木が空なら、根ノードを初期化する + if (bst->root == NULL) { + bst->root = newTreeNode(num); + return; + } + TreeNode *cur = bst->root, *pre = NULL; + // ループで探索し、葉ノードを越えたら抜ける + while (cur != NULL) { + // 重複ノードが見つかったら、直ちに返す + if (cur->val == num) { + return; + } + pre = cur; + if (cur->val < num) { + // 挿入位置は cur の右部分木にある + cur = cur->right; + } else { + // 挿入位置は cur の左部分木にある + cur = cur->left; + } + } + // ノードを挿入 + TreeNode *node = newTreeNode(num); + if (pre->val < num) { + pre->right = node; + } else { + pre->left = node; + } +} + +/* ノードを削除 */ +// stdio.h を導入しているため、ここでは remove 識別子を使えない +void removeItem(BinarySearchTree *bst, int num) { + // 木が空なら、そのまま早期リターンする + if (bst->root == NULL) + return; + TreeNode *cur = bst->root, *pre = NULL; + // ループで探索し、葉ノードを越えたら抜ける + while (cur != NULL) { + // 削除対象のノードが見つかったら、ループを抜ける + if (cur->val == num) + break; + pre = cur; + if (cur->val < num) { + // 削除対象ノードは root の右部分木にある + cur = cur->right; + } else { + // 削除対象ノードは root の左部分木にある + cur = cur->left; + } + } + // 削除対象ノードがなければそのまま返す + if (cur == NULL) + return; + // 削除対象ノードに子ノードがあるかを判定する + if (cur->left == NULL || cur->right == NULL) { + /* 子ノード数 = 0 or 1 */ + // 子ノード数 = 0 / 1 のとき、child = nullptr / その子ノード + TreeNode *child = cur->left != NULL ? cur->left : cur->right; + // ノード cur を削除する + if (pre->left == cur) { + pre->left = child; + } else { + pre->right = child; + } + // メモリを解放する + free(cur); + } else { + /* 子ノード数 = 2 */ + // 中順走査における cur の次ノードを取得 + TreeNode *tmp = cur->right; + while (tmp->left != NULL) { + tmp = tmp->left; + } + int tmpVal = tmp->val; + // ノード tmp を再帰的に削除 + removeItem(bst, tmp->val); + // tmp で cur を上書きする + cur->val = tmpVal; + } +} + +/* Driver Code */ +int main() { + /* 二分探索木を初期化 */ + int nums[] = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; + BinarySearchTree *bst = newBinarySearchTree(); + for (int i = 0; i < sizeof(nums) / sizeof(int); i++) { + insert(bst, nums[i]); + } + printf("初期化した二分木は\n"); + printTree(getRoot(bst)); + + /* ノードを探索 */ + TreeNode *node = search(bst, 7); + printf("見つかったノードオブジェクトのノード値 = %d\n", node->val); + + /* ノードを挿入 */ + insert(bst, 16); + printf("ノード 16 を挿入した後、二分木は\n"); + printTree(getRoot(bst)); + + /* ノードを削除 */ + removeItem(bst, 1); + printf("ノード 1 を削除した後、二分木は\n"); + printTree(getRoot(bst)); + removeItem(bst, 2); + printf("ノード 2 を削除した後、二分木は\n"); + printTree(getRoot(bst)); + removeItem(bst, 4); + printf("ノード 4 を削除した後、二分木は\n"); + printTree(getRoot(bst)); + + // メモリを解放する + delBinarySearchTree(bst); + return 0; +} diff --git a/ja/codes/c/chapter_tree/binary_tree.c b/ja/codes/c/chapter_tree/binary_tree.c new file mode 100644 index 000000000..26b143e0c --- /dev/null +++ b/ja/codes/c/chapter_tree/binary_tree.c @@ -0,0 +1,43 @@ +/** + * File: binary_tree.c + * Created Time: 2023-01-11 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +/* Driver Code */ +int main() { + /* 二分木を初期化 */ + // ノードを初期化 + TreeNode *n1 = newTreeNode(1); + TreeNode *n2 = newTreeNode(2); + TreeNode *n3 = newTreeNode(3); + TreeNode *n4 = newTreeNode(4); + TreeNode *n5 = newTreeNode(5); + // ノード間の参照(ポインタ)を構築する + n1->left = n2; + n1->right = n3; + n2->left = n4; + n2->right = n5; + printf("二分木を初期化\n"); + printTree(n1); + + /* ノードの挿入と削除 */ + TreeNode *P = newTreeNode(0); + // n1 -> n2 の間にノード P を挿入 + n1->left = P; + P->left = n2; + printf("ノード P を挿入した後\n"); + printTree(n1); + + // ノード P を削除 + n1->left = n2; + // メモリを解放する + free(P); + printf("ノード P を削除した後\n"); + printTree(n1); + + freeMemoryTree(n1); + return 0; +} diff --git a/ja/codes/c/chapter_tree/binary_tree_bfs.c b/ja/codes/c/chapter_tree/binary_tree_bfs.c new file mode 100644 index 000000000..7d976e4a7 --- /dev/null +++ b/ja/codes/c/chapter_tree/binary_tree_bfs.c @@ -0,0 +1,73 @@ +/** + * File: binary_tree_bfs.c + * Created Time: 2023-01-11 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 + +/* レベル順走査 */ +int *levelOrder(TreeNode *root, int *size) { + /* 補助キュー */ + int front, rear; + int index, *arr; + TreeNode *node; + TreeNode **queue; + + /* 補助キュー */ + queue = (TreeNode **)malloc(sizeof(TreeNode *) * MAX_SIZE); + // キューへのポインタ + front = 0, rear = 0; + // 根ノードを追加する + queue[rear++] = root; + // 走査順序を保存するためのリストを初期化する + /* 補助配列 */ + arr = (int *)malloc(sizeof(int) * MAX_SIZE); + // 配列ポインタ + index = 0; + while (front < rear) { + // デキュー + node = queue[front++]; + // ノードの値を保存する + arr[index++] = node->val; + if (node->left != NULL) { + // 左子ノードをキューに追加 + queue[rear++] = node->left; + } + if (node->right != NULL) { + // 右子ノードをキューに追加 + queue[rear++] = node->right; + } + } + // 配列長の値を更新 + *size = index; + arr = realloc(arr, sizeof(int) * (*size)); + + // 補助配列の領域を解放する + free(queue); + return arr; +} + +/* Driver Code */ +int main() { + /* 二分木を初期化 */ + // ここでは、配列から直接二分木を生成する関数を利用する + int nums[] = {1, 2, 3, 4, 5, 6, 7}; + int size = sizeof(nums) / sizeof(int); + TreeNode *root = arrayToTree(nums, size); + printf("二分木を初期化\n"); + printTree(root); + + /* レベル順走査 */ + // 配列の長さを渡す必要がある + int *arr = levelOrder(root, &size); + printf("レベル順走査のノード出力シーケンス = "); + printArray(arr, size); + + // メモリを解放する + freeMemoryTree(root); + free(arr); + return 0; +} diff --git a/ja/codes/c/chapter_tree/binary_tree_dfs.c b/ja/codes/c/chapter_tree/binary_tree_dfs.c new file mode 100644 index 000000000..5ae6a7d22 --- /dev/null +++ b/ja/codes/c/chapter_tree/binary_tree_dfs.c @@ -0,0 +1,75 @@ +/** + * File: binary_tree_dfs.c + * Created Time: 2023-01-11 + * Author: Reanon (793584285@qq.com) + */ + +#include "../utils/common.h" + +#define MAX_SIZE 100 + +// 走査順序を格納するための補助配列 +int arr[MAX_SIZE]; + +/* 先行順走査 */ +void preOrder(TreeNode *root, int *size) { + if (root == NULL) + return; + // 訪問順序:根ノード -> 左部分木 -> 右部分木 + arr[(*size)++] = root->val; + preOrder(root->left, size); + preOrder(root->right, size); +} + +/* 中順走査 */ +void inOrder(TreeNode *root, int *size) { + if (root == NULL) + return; + // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 + inOrder(root->left, size); + arr[(*size)++] = root->val; + inOrder(root->right, size); +} + +/* 後順走査 */ +void postOrder(TreeNode *root, int *size) { + if (root == NULL) + return; + // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード + postOrder(root->left, size); + postOrder(root->right, size); + arr[(*size)++] = root->val; +} + +/* Driver Code */ +int main() { + /* 二分木を初期化 */ + // ここでは、配列から直接二分木を生成する関数を利用する + int nums[] = {1, 2, 3, 4, 5, 6, 7}; + int size = sizeof(nums) / sizeof(int); + TreeNode *root = arrayToTree(nums, size); + printf("二分木を初期化\n"); + printTree(root); + + /* 先行順走査 */ + // 補助配列を初期化する + size = 0; + preOrder(root, &size); + printf("前順走査のノード出力シーケンス = "); + printArray(arr, size); + + /* 中順走査 */ + size = 0; + inOrder(root, &size); + printf("中順走査のノード出力シーケンス = "); + printArray(arr, size); + + /* 後順走査 */ + size = 0; + postOrder(root, &size); + printf("後順走査のノード出力シーケンス = "); + printArray(arr, size); + + freeMemoryTree(root); + return 0; +} diff --git a/ja/codes/c/utils/CMakeLists.txt b/ja/codes/c/utils/CMakeLists.txt new file mode 100644 index 000000000..c1ece2e38 --- /dev/null +++ b/ja/codes/c/utils/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(utils + common_test.c + common.h print_util.h + list_node.h tree_node.h + uthash.h) \ No newline at end of file diff --git a/ja/codes/c/utils/common.h b/ja/codes/c/utils/common.h new file mode 100644 index 000000000..8b9adeff7 --- /dev/null +++ b/ja/codes/c/utils/common.h @@ -0,0 +1,36 @@ +/** + * File: common.h + * Created Time: 2022-12-20 + * Author: MolDuM (moldum@163.com)、Reanon (793584285@qq.com) + */ + +#ifndef COMMON_H +#define COMMON_H + +#include +#include +#include +#include +#include +#include +#include + +#include "list_node.h" +#include "print_util.h" +#include "tree_node.h" +#include "vertex.h" + +// hash table lib +#include "uthash.h" + +#include "vector.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif // COMMON_H diff --git a/ja/codes/c/utils/common_test.c b/ja/codes/c/utils/common_test.c new file mode 100644 index 000000000..a889b423b --- /dev/null +++ b/ja/codes/c/utils/common_test.c @@ -0,0 +1,35 @@ +/** + * File: include_test.c + * Created Time: 2023-01-10 + * Author: Reanon (793584285@qq.com) + */ + +#include "common.h" + +void testListNode() { + int nums[] = {2, 3, 5, 6, 7}; + int size = sizeof(nums) / sizeof(int); + ListNode *head = arrToLinkedList(nums, size); + printLinkedList(head); +} + +void testTreeNode() { + int nums[] = {1, 2, 3, INT_MAX, 5, 6, INT_MAX}; + int size = sizeof(nums) / sizeof(int); + TreeNode *root = arrayToTree(nums, size); + + // print tree + printTree(root); + + // tree to arr + int *arr = treeToArray(root, &size); + printArray(arr, size); +} + +int main(int argc, char *argv[]) { + printf("==testListNode==\n"); + testListNode(); + printf("==testTreeNode==\n"); + testTreeNode(); + return 0; +} diff --git a/ja/codes/c/utils/list_node.h b/ja/codes/c/utils/list_node.h new file mode 100644 index 000000000..1de41fdb7 --- /dev/null +++ b/ja/codes/c/utils/list_node.h @@ -0,0 +1,59 @@ +/** + * File: list_node.h + * Created Time: 2023-01-09 + * Author: Reanon (793584285@qq.com) + */ + +#ifndef LIST_NODE_H +#define LIST_NODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* 連結リストノード構造体 */ +typedef struct ListNode { + int val; // ノード値 + struct ListNode *next; // 次のノードへの参照 +} ListNode; + +/* コンストラクタ。新しいノードを初期化する */ +ListNode *newListNode(int val) { + ListNode *node; + node = (ListNode *)malloc(sizeof(ListNode)); + node->val = val; + node->next = NULL; + return node; +} + +/* 配列をデシリアライズして連結リストに変換する */ +ListNode *arrToLinkedList(const int *arr, size_t size) { + if (size <= 0) { + return NULL; + } + + ListNode *dummy = newListNode(0); + ListNode *node = dummy; + for (int i = 0; i < size; i++) { + node->next = newListNode(arr[i]); + node = node->next; + } + return dummy->next; +} + +/* 連結リストに割り当てたメモリを解放する */ +void freeMemoryLinkedList(ListNode *cur) { + // メモリを解放する + ListNode *pre; + while (cur != NULL) { + pre = cur; + cur = cur->next; + free(pre); + } +} + +#ifdef __cplusplus +} +#endif + +#endif // LIST_NODE_H diff --git a/ja/codes/c/utils/print_util.h b/ja/codes/c/utils/print_util.h new file mode 100644 index 000000000..b0be4c263 --- /dev/null +++ b/ja/codes/c/utils/print_util.h @@ -0,0 +1,131 @@ +/** + * File: print_util.h + * Created Time: 2022-12-21 + * Author: MolDum (moldum@163.com), Reanon (793584285@qq.com) + */ + +#ifndef PRINT_UTIL_H +#define PRINT_UTIL_H + +#include +#include +#include + +#include "list_node.h" +#include "tree_node.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* 配列を出力する */ +void printArray(int arr[], int size) { + if (arr == NULL || size == 0) { + printf("[]"); + return; + } + printf("["); + for (int i = 0; i < size - 1; i++) { + printf("%d, ", arr[i]); + } + printf("%d]\n", arr[size - 1]); +} + +/* 配列を出力する */ +void printArrayFloat(float arr[], int size) { + if (arr == NULL || size == 0) { + printf("[]"); + return; + } + printf("["); + for (int i = 0; i < size - 1; i++) { + printf("%.2f, ", arr[i]); + } + printf("%.2f]\n", arr[size - 1]); +} + +/* 連結リストを出力 */ +void printLinkedList(ListNode *node) { + if (node == NULL) { + return; + } + while (node->next != NULL) { + printf("%d -> ", node->val); + node = node->next; + } + printf("%d\n", node->val); +} + +typedef struct Trunk { + struct Trunk *prev; + char *str; +} Trunk; + +Trunk *newTrunk(Trunk *prev, char *str) { + Trunk *trunk = (Trunk *)malloc(sizeof(Trunk)); + trunk->prev = prev; + trunk->str = (char *)malloc(sizeof(char) * 10); + strcpy(trunk->str, str); + return trunk; +} + +void showTrunks(Trunk *trunk) { + if (trunk == NULL) { + return; + } + showTrunks(trunk->prev); + printf("%s", trunk->str); +} + +/** + * 二分木を出力 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +void printTreeHelper(TreeNode *node, Trunk *prev, bool isRight) { + if (node == NULL) { + return; + } + char *prev_str = " "; + Trunk *trunk = newTrunk(prev, prev_str); + printTreeHelper(node->right, trunk, true); + if (prev == NULL) { + trunk->str = "———"; + } else if (isRight) { + trunk->str = "/———"; + prev_str = " |"; + } else { + trunk->str = "\\———"; + prev->str = prev_str; + } + showTrunks(trunk); + printf("%d\n", node->val); + + if (prev != NULL) { + prev->str = prev_str; + } + trunk->str = " |"; + + printTreeHelper(node->left, trunk, false); +} + +/* 二分木を出力 */ +void printTree(TreeNode *root) { + printTreeHelper(root, NULL, false); +} + +/* ヒープを出力 */ +void printHeap(int arr[], int size) { + TreeNode *root; + printf("ヒープの配列表現:"); + printArray(arr, size); + printf("ヒープの木構造表現:\n"); + root = arrayToTree(arr, size); + printTree(root); +} + +#ifdef __cplusplus +} +#endif + +#endif // PRINT_UTIL_H diff --git a/ja/codes/c/utils/tree_node.h b/ja/codes/c/utils/tree_node.h new file mode 100644 index 000000000..c80319d4d --- /dev/null +++ b/ja/codes/c/utils/tree_node.h @@ -0,0 +1,107 @@ +/** + * File: tree_node.h + * Created Time: 2023-01-09 + * Author: Reanon (793584285@qq.com) + */ + +#ifndef TREE_NODE_H +#define TREE_NODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define MAX_NODE_SIZE 5000 + +/* 二分木ノード構造体 */ +typedef struct TreeNode { + int val; // ノード値 + int height; // ノードの高さ + struct TreeNode *left; // 左の子ノードへのポインタ + struct TreeNode *right; // 右の子ノードへのポインタ +} TreeNode; + +/* コンストラクタ */ +TreeNode *newTreeNode(int val) { + TreeNode *node; + + node = (TreeNode *)malloc(sizeof(TreeNode)); + node->val = val; + node->height = 0; + node->left = NULL; + node->right = NULL; + return node; +} + +// シリアライズの符号化規則は以下を参照: +// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ +// 二分木の配列表現: +// [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] +// 二分木の連結リスト表現: +// /——— 15 +// /——— 7 +// /——— 3 +// | \——— 6 +// | \——— 12 +// ——— 1 +// \——— 2 +// | /——— 9 +// \——— 4 +// \——— 8 + +/* リストを二分木にデシリアライズする: 再帰 */ +TreeNode *arrayToTreeDFS(int *arr, int size, int i) { + if (i < 0 || i >= size || arr[i] == INT_MAX) { + return NULL; + } + TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); + root->val = arr[i]; + root->left = arrayToTreeDFS(arr, size, 2 * i + 1); + root->right = arrayToTreeDFS(arr, size, 2 * i + 2); + return root; +} + +/* リストを二分木にデシリアライズする */ +TreeNode *arrayToTree(int *arr, int size) { + return arrayToTreeDFS(arr, size, 0); +} + +/* 二分木をリストにシリアライズする: 再帰 */ +void treeToArrayDFS(TreeNode *root, int i, int *res, int *size) { + if (root == NULL) { + return; + } + while (i >= *size) { + res = realloc(res, (*size + 1) * sizeof(int)); + res[*size] = INT_MAX; + (*size)++; + } + res[i] = root->val; + treeToArrayDFS(root->left, 2 * i + 1, res, size); + treeToArrayDFS(root->right, 2 * i + 2, res, size); +} + +/* 二分木をリストにシリアライズする */ +int *treeToArray(TreeNode *root, int *size) { + *size = 0; + int *res = NULL; + treeToArrayDFS(root, 0, res, size); + return res; +} + +/* 二分木のメモリを解放する */ +void freeMemoryTree(TreeNode *root) { + if (root == NULL) + return; + freeMemoryTree(root->left); + freeMemoryTree(root->right); + free(root); +} + +#ifdef __cplusplus +} +#endif + +#endif // TREE_NODE_H diff --git a/ja/codes/c/utils/uthash.h b/ja/codes/c/utils/uthash.h new file mode 100644 index 000000000..68693bf39 --- /dev/null +++ b/ja/codes/c/utils/uthash.h @@ -0,0 +1,1140 @@ +/* +Copyright (c) 2003-2022, Troy D. Hanson https://troydhanson.github.io/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#define UTHASH_VERSION 2.3.0 + +#include /* memcmp, memset, strlen */ +#include /* ptrdiff_t */ +#include /* exit */ + +#if defined(HASH_DEFINE_OWN_STDINT) && HASH_DEFINE_OWN_STDINT +/* This codepath is provided for backward compatibility, but I plan to remove it. */ +#warning "HASH_DEFINE_OWN_STDINT is deprecated; please use HASH_NO_STDINT instead" +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +#elif defined(HASH_NO_STDINT) && HASH_NO_STDINT +#else +#include /* uint8_t, uint32_t */ +#endif + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#if !defined(DECLTYPE) && !defined(NO_DECLTYPE) +#if defined(_MSC_VER) /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#endif +#elif defined(__MCST__) /* Elbrus C Compiler */ +#define DECLTYPE(x) (__typeof(x)) +#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__) +#define NO_DECLTYPE +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE(x) +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + char **_da_dst = (char**)(&(dst)); \ + *_da_dst = (char*)(src); \ +} while (0) +#else +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + (dst) = DECLTYPE(dst)(src); \ +} while (0) +#endif + +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr,sz) free(ptr) /* free fcn */ +#endif +#ifndef uthash_bzero +#define uthash_bzero(a,n) memset(a,'\0',n) +#endif +#ifndef uthash_strlen +#define uthash_strlen(s) strlen(s) +#endif + +#ifndef HASH_FUNCTION +#define HASH_FUNCTION(keyptr,keylen,hashv) HASH_JEN(keyptr, keylen, hashv) +#endif + +#ifndef HASH_KEYCMP +#define HASH_KEYCMP(a,b,n) memcmp(a,b,n) +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +#ifndef HASH_NONFATAL_OOM +#define HASH_NONFATAL_OOM 0 +#endif + +#if HASH_NONFATAL_OOM +/* malloc failures can be recovered from */ + +#ifndef uthash_nonfatal_oom +#define uthash_nonfatal_oom(obj) do {} while (0) /* non-fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) do { (oomed) = 1; } while (0) +#define IF_HASH_NONFATAL_OOM(x) x + +#else +/* malloc failures result in lost memory, hash tables are unusable */ + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal OOM error */ +#endif + +#define HASH_RECORD_OOM(oomed) uthash_fatal("out of memory") +#define IF_HASH_NONFATAL_OOM(x) + +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32U /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */ +#define HASH_BKT_CAPACITY_THRESH 10U /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhp */ +#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) +/* calculate the hash handle from element address elp */ +#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle*)(void*)(((char*)(elp)) + ((tbl)->hho))) + +#define HASH_ROLLBACK_BKT(hh, head, itemptrhh) \ +do { \ + struct UT_hash_handle *_hd_hh_item = (itemptrhh); \ + unsigned _hd_bkt; \ + HASH_TO_BKT(_hd_hh_item->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + (head)->hh.tbl->buckets[_hd_bkt].count++; \ + _hd_hh_item->hh_next = NULL; \ + _hd_hh_item->hh_prev = NULL; \ +} while (0) + +#define HASH_VALUE(keyptr,keylen,hashv) \ +do { \ + HASH_FUNCTION(keyptr, keylen, hashv); \ +} while (0) + +#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_bkt; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \ + } \ + } \ +} while (0) + +#define HASH_FIND(hh,head,keyptr,keylen,out) \ +do { \ + (out) = NULL; \ + if (head) { \ + unsigned _hf_hashv; \ + HASH_VALUE(keyptr, keylen, _hf_hashv); \ + HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out); \ + } \ +} while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL) +#define HASH_BLOOM_MAKE(tbl,oomed) \ +do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!(tbl)->bloom_bv) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ + } \ +} while (0) + +#define HASH_BLOOM_FREE(tbl) \ +do { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ +} while (0) + +#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U))) +#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U))) + +#define HASH_BLOOM_ADD(tbl,hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#define HASH_BLOOM_TEST(tbl,hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, ((hashv) & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U))) + +#else +#define HASH_BLOOM_MAKE(tbl,oomed) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl,hashv) +#define HASH_BLOOM_TEST(tbl,hashv) (1) +#define HASH_BLOOM_BYTELEN 0U +#endif + +#define HASH_MAKE_TABLE(hh,head,oomed) \ +do { \ + (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table)); \ + if (!(head)->hh.tbl) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ + if (!(head)->hh.tbl->buckets) { \ + HASH_RECORD_OOM(oomed); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } else { \ + uthash_bzero((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + uthash_free((head)->hh.tbl->buckets, \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + } \ + ) \ + } \ + } \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \ +} while (0) + +#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \ +do { \ + (replaced) = NULL; \ + HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \ + if (replaced) { \ + HASH_DELETE(hh, head, replaced); \ + } \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \ +} while (0) + +#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \ +} while (0) + +#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn) \ +do { \ + unsigned _hr_hashv; \ + HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv); \ + HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \ +} while (0) + +#define HASH_APPEND_LIST(hh, head, add) \ +do { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail->next = (add); \ + (head)->hh.tbl->tail = &((add)->hh); \ +} while (0) + +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + do { \ + if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) { \ + break; \ + } \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) + +#ifdef NO_DECLTYPE +#undef HASH_AKBI_INNER_LOOP +#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn) \ +do { \ + char *_hs_saved_head = (char*)(head); \ + do { \ + DECLTYPE_ASSIGN(head, _hs_iter); \ + if (cmpfcn(head, add) > 0) { \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + break; \ + } \ + DECLTYPE_ASSIGN(head, _hs_saved_head); \ + } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next)); \ +} while (0) +#endif + +#if HASH_NONFATAL_OOM + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + if (!(oomed)) { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + if (oomed) { \ + HASH_ROLLBACK_BKT(hh, head, &(add)->hh); \ + HASH_DELETE_HH(hh, head, &(add)->hh); \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } else { \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ + } \ + } else { \ + (add)->hh.tbl = NULL; \ + uthash_nonfatal_oom(add); \ + } \ +} while (0) + +#else + +#define HASH_ADD_TO_TABLE(hh,head,keyptr,keylen_in,hashval,add,oomed) \ +do { \ + unsigned _ha_bkt; \ + (head)->hh.tbl->num_items++; \ + HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], hh, &(add)->hh, oomed); \ + HASH_BLOOM_ADD((head)->hh.tbl, hashval); \ + HASH_EMIT_KEY(hh, head, keyptr, keylen_in); \ +} while (0) + +#endif + + +#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (char*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + void *_hs_iter = (head); \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn); \ + if (_hs_iter) { \ + (add)->hh.next = _hs_iter; \ + if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) { \ + HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add); \ + } else { \ + (head) = (add); \ + } \ + HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add); \ + } else { \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE_INORDER"); \ +} while (0) + +#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn) \ +do { \ + unsigned _hs_hashv; \ + HASH_VALUE(keyptr, keylen_in, _hs_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \ + HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn) + +#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn) \ + HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn) + +#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add) \ +do { \ + IF_HASH_NONFATAL_OOM( int _ha_oomed = 0; ) \ + (add)->hh.hashv = (hashval); \ + (add)->hh.key = (const void*) (keyptr); \ + (add)->hh.keylen = (unsigned) (keylen_in); \ + if (!(head)) { \ + (add)->hh.next = NULL; \ + (add)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh, add, _ha_oomed); \ + IF_HASH_NONFATAL_OOM( if (!_ha_oomed) { ) \ + (head) = (add); \ + IF_HASH_NONFATAL_OOM( } ) \ + } else { \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_APPEND_LIST(hh, head, add); \ + } \ + HASH_ADD_TO_TABLE(hh, head, keyptr, keylen_in, hashval, add, _ha_oomed); \ + HASH_FSCK(hh, head, "HASH_ADD_KEYPTR_BYHASHVALUE"); \ +} while (0) + +#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ +do { \ + unsigned _ha_hashv; \ + HASH_VALUE(keyptr, keylen_in, _ha_hashv); \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add); \ +} while (0) + +#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add) \ + HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add) + +#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ + HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add) + +#define HASH_TO_BKT(hashv,num_bkts,bkt) \ +do { \ + bkt = ((hashv) & ((num_bkts) - 1U)); \ +} while (0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh,head,delptr) \ + HASH_DELETE_HH(hh, head, &(delptr)->hh) + +#define HASH_DELETE_HH(hh,head,delptrhh) \ +do { \ + const struct UT_hash_handle *_hd_hh_del = (delptrhh); \ + if ((_hd_hh_del->prev == NULL) && (_hd_hh_del->next == NULL)) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } else { \ + unsigned _hd_bkt; \ + if (_hd_hh_del == (head)->hh.tbl->tail) { \ + (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev); \ + } \ + if (_hd_hh_del->prev != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->prev)->next = _hd_hh_del->next; \ + } else { \ + DECLTYPE_ASSIGN(head, _hd_hh_del->next); \ + } \ + if (_hd_hh_del->next != NULL) { \ + HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev; \ + } \ + HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT((head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh, head, "HASH_DELETE_HH"); \ +} while (0) + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head,findstr,out) \ +do { \ + unsigned _uthash_hfstr_keylen = (unsigned)uthash_strlen(findstr); \ + HASH_FIND(hh, head, findstr, _uthash_hfstr_keylen, out); \ +} while (0) +#define HASH_ADD_STR(head,strfield,add) \ +do { \ + unsigned _uthash_hastr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_ADD(hh, head, strfield[0], _uthash_hastr_keylen, add); \ +} while (0) +#define HASH_REPLACE_STR(head,strfield,add,replaced) \ +do { \ + unsigned _uthash_hrstr_keylen = (unsigned)uthash_strlen((add)->strfield); \ + HASH_REPLACE(hh, head, strfield[0], _uthash_hrstr_keylen, add, replaced); \ +} while (0) +#define HASH_FIND_INT(head,findint,out) \ + HASH_FIND(hh,head,findint,sizeof(int),out) +#define HASH_ADD_INT(head,intfield,add) \ + HASH_ADD(hh,head,intfield,sizeof(int),add) +#define HASH_REPLACE_INT(head,intfield,add,replaced) \ + HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) +#define HASH_FIND_PTR(head,findptr,out) \ + HASH_FIND(hh,head,findptr,sizeof(void *),out) +#define HASH_ADD_PTR(head,ptrfield,add) \ + HASH_ADD(hh,head,ptrfield,sizeof(void *),add) +#define HASH_REPLACE_PTR(head,ptrfield,add,replaced) \ + HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) +#define HASH_DEL(head,delptr) \ + HASH_DELETE(hh,head,delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. + * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. + */ +#ifdef HASH_DEBUG +#include /* fprintf, stderr */ +#define HASH_OOPS(...) do { fprintf(stderr, __VA_ARGS__); exit(-1); } while (0) +#define HASH_FSCK(hh,head,where) \ +do { \ + struct UT_hash_handle *_thh; \ + if (head) { \ + unsigned _bkt_i; \ + unsigned _count = 0; \ + char *_prev; \ + for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) { \ + unsigned _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char*)(_thh->hh_prev)) { \ + HASH_OOPS("%s: invalid hh_prev %p, actual %p\n", \ + (where), (void*)_thh->hh_prev, (void*)_prev); \ + } \ + _bkt_count++; \ + _prev = (char*)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("%s: invalid bucket count %u, actual %u\n", \ + (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid hh item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev != (char*)_thh->prev) { \ + HASH_OOPS("%s: invalid prev %p, actual %p\n", \ + (where), (void*)_thh->prev, (void*)_prev); \ + } \ + _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("%s: invalid app item count %u, actual %u\n", \ + (where), (head)->hh.tbl->num_items, _count); \ + } \ + } \ +} while (0) +#else +#define HASH_FSCK(hh,head,where) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ +do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen); \ +} while (0) +#else +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */ +#define HASH_BER(key,keylen,hashv) \ +do { \ + unsigned _hb_keylen = (unsigned)keylen; \ + const unsigned char *_hb_key = (const unsigned char*)(key); \ + (hashv) = 0; \ + while (_hb_keylen-- != 0U) { \ + (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++; \ + } \ +} while (0) + + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx + * (archive link: https://archive.is/Ivcan ) + */ +#define HASH_SAX(key,keylen,hashv) \ +do { \ + unsigned _sx_i; \ + const unsigned char *_hs_key = (const unsigned char*)(key); \ + hashv = 0; \ + for (_sx_i=0; _sx_i < keylen; _sx_i++) { \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + } \ +} while (0) +/* FNV-1a variation */ +#define HASH_FNV(key,keylen,hashv) \ +do { \ + unsigned _fn_i; \ + const unsigned char *_hf_key = (const unsigned char*)(key); \ + (hashv) = 2166136261U; \ + for (_fn_i=0; _fn_i < keylen; _fn_i++) { \ + hashv = hashv ^ _hf_key[_fn_i]; \ + hashv = hashv * 16777619U; \ + } \ +} while (0) + +#define HASH_OAT(key,keylen,hashv) \ +do { \ + unsigned _ho_i; \ + const unsigned char *_ho_key=(const unsigned char*)(key); \ + hashv = 0; \ + for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ +} while (0) + +#define HASH_JEN_MIX(a,b,c) \ +do { \ + a -= b; a -= c; a ^= ( c >> 13 ); \ + b -= c; b -= a; b ^= ( a << 8 ); \ + c -= a; c -= b; c ^= ( b >> 13 ); \ + a -= b; a -= c; a ^= ( c >> 12 ); \ + b -= c; b -= a; b ^= ( a << 16 ); \ + c -= a; c -= b; c ^= ( b >> 5 ); \ + a -= b; a -= c; a ^= ( c >> 3 ); \ + b -= c; b -= a; b ^= ( a << 10 ); \ + c -= a; c -= b; c ^= ( b >> 15 ); \ +} while (0) + +#define HASH_JEN(key,keylen,hashv) \ +do { \ + unsigned _hj_i,_hj_j,_hj_k; \ + unsigned const char *_hj_key=(unsigned const char*)(key); \ + hashv = 0xfeedbeefu; \ + _hj_i = _hj_j = 0x9e3779b9u; \ + _hj_k = (unsigned)(keylen); \ + while (_hj_k >= 12U) { \ + _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + + ( (unsigned)_hj_key[2] << 16 ) \ + + ( (unsigned)_hj_key[3] << 24 ) ); \ + _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + + ( (unsigned)_hj_key[6] << 16 ) \ + + ( (unsigned)_hj_key[7] << 24 ) ); \ + hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + + ( (unsigned)_hj_key[10] << 16 ) \ + + ( (unsigned)_hj_key[11] << 24 ) ); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12U; \ + } \ + hashv += (unsigned)(keylen); \ + switch ( _hj_k ) { \ + case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */ \ + case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); /* FALLTHROUGH */ \ + case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); /* FALLTHROUGH */ \ + case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); /* FALLTHROUGH */ \ + case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); /* FALLTHROUGH */ \ + case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); /* FALLTHROUGH */ \ + case 5: _hj_j += _hj_key[4]; /* FALLTHROUGH */ \ + case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); /* FALLTHROUGH */ \ + case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); /* FALLTHROUGH */ \ + case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); /* FALLTHROUGH */ \ + case 1: _hj_i += _hj_key[0]; /* FALLTHROUGH */ \ + default: ; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ +} while (0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif +#define HASH_SFH(key,keylen,hashv) \ +do { \ + unsigned const char *_sfh_key=(unsigned const char*)(key); \ + uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen; \ + \ + unsigned _sfh_rem = _sfh_len & 3U; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabeu; \ + \ + /* Main loop */ \ + for (;_sfh_len > 0U; _sfh_len--) { \ + hashv += get16bits (_sfh_key); \ + _sfh_tmp = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2U*sizeof (uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18; \ + hashv += hashv >> 11; \ + break; \ + case 2: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + break; \ + default: ; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ +} while (0) + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out) \ +do { \ + if ((head).hh_head != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head)); \ + } else { \ + (out) = NULL; \ + } \ + while ((out) != NULL) { \ + if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) { \ + if (HASH_KEYCMP((out)->hh.key, keyptr, keylen_in) == 0) { \ + break; \ + } \ + } \ + if ((out)->hh.hh_next != NULL) { \ + DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next)); \ + } else { \ + (out) = NULL; \ + } \ + } \ +} while (0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head,hh,addhh,oomed) \ +do { \ + UT_hash_bucket *_ha_head = &(head); \ + _ha_head->count++; \ + (addhh)->hh_next = _ha_head->hh_head; \ + (addhh)->hh_prev = NULL; \ + if (_ha_head->hh_head != NULL) { \ + _ha_head->hh_head->hh_prev = (addhh); \ + } \ + _ha_head->hh_head = (addhh); \ + if ((_ha_head->count >= ((_ha_head->expand_mult + 1U) * HASH_BKT_CAPACITY_THRESH)) \ + && !(addhh)->tbl->noexpand) { \ + HASH_EXPAND_BUCKETS(addhh,(addhh)->tbl, oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (oomed) { \ + HASH_DEL_IN_BKT(head,addhh); \ + } \ + ) \ + } \ +} while (0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(head,delhh) \ +do { \ + UT_hash_bucket *_hd_head = &(head); \ + _hd_head->count--; \ + if (_hd_head->hh_head == (delhh)) { \ + _hd_head->hh_head = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_prev) { \ + (delhh)->hh_prev->hh_next = (delhh)->hh_next; \ + } \ + if ((delhh)->hh_next) { \ + (delhh)->hh_next->hh_prev = (delhh)->hh_prev; \ + } \ +} while (0) + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(hh,tbl,oomed) \ +do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + if (!_he_new_buckets) { \ + HASH_RECORD_OOM(oomed); \ + } else { \ + uthash_bzero(_he_new_buckets, \ + sizeof(struct UT_hash_bucket) * (tbl)->num_buckets * 2U); \ + (tbl)->ideal_chain_maxlen = \ + ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) + \ + ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U); \ + (tbl)->nonideal_items = 0; \ + for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) { \ + _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head; \ + while (_he_thh != NULL) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[_he_bkt]); \ + if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) { \ + (tbl)->nonideal_items++; \ + if (_he_newbkt->count > _he_newbkt->expand_mult * (tbl)->ideal_chain_maxlen) { \ + _he_newbkt->expand_mult++; \ + } \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head != NULL) { \ + _he_newbkt->hh_head->hh_prev = _he_thh; \ + } \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \ + (tbl)->num_buckets *= 2U; \ + (tbl)->log2_num_buckets++; \ + (tbl)->buckets = _he_new_buckets; \ + (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ? \ + ((tbl)->ineff_expands+1U) : 0U; \ + if ((tbl)->ineff_expands > 1U) { \ + (tbl)->noexpand = 1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ + } \ +} while (0) + + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) +#define HASH_SRT(hh,head,cmpfcn) \ +do { \ + unsigned _hs_i; \ + unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head != NULL) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping != 0U) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p != NULL) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) { \ + _hs_psize++; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + if (_hs_q == NULL) { \ + break; \ + } \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) { \ + if (_hs_psize == 0U) { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else if ((cmpfcn( \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q)) \ + )) <= 0) { \ + _hs_e = _hs_p; \ + if (_hs_p != NULL) { \ + _hs_p = ((_hs_p->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL); \ + } \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = ((_hs_q->next != NULL) ? \ + HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL); \ + _hs_qsize--; \ + } \ + if ( _hs_tail != NULL ) { \ + _hs_tail->next = ((_hs_e != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + if (_hs_e != NULL) { \ + _hs_e->prev = ((_hs_tail != NULL) ? \ + ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL); \ + } \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + if (_hs_tail != NULL) { \ + _hs_tail->next = NULL; \ + } \ + if (_hs_nmerges <= 1U) { \ + _hs_looping = 0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2U; \ + } \ + HASH_FSCK(hh, head, "HASH_SRT"); \ + } \ +} while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ +do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt = NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ + ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ + if ((src) != NULL) { \ + for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ + for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh != NULL; \ + _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + IF_HASH_NONFATAL_OOM( int _hs_oomed = 0; ) \ + _dst_hh = (UT_hash_handle*)(void*)(((char*)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh != NULL) { \ + _last_elt_hh->next = _elt; \ + } \ + if ((dst) == NULL) { \ + DECLTYPE_ASSIGN(dst, _elt); \ + HASH_MAKE_TABLE(hh_dst, dst, _hs_oomed); \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + uthash_nonfatal_oom(_elt); \ + (dst) = NULL; \ + continue; \ + } \ + ) \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], hh_dst, _dst_hh, _hs_oomed); \ + (dst)->hh_dst.tbl->num_items++; \ + IF_HASH_NONFATAL_OOM( \ + if (_hs_oomed) { \ + HASH_ROLLBACK_BKT(hh_dst, dst, _dst_hh); \ + HASH_DELETE_HH(hh_dst, dst, _dst_hh); \ + _dst_hh->tbl = NULL; \ + uthash_nonfatal_oom(_elt); \ + continue; \ + } \ + ) \ + HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv); \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst, dst, "HASH_SELECT"); \ +} while (0) + +#define HASH_CLEAR(hh,head) \ +do { \ + if ((head) != NULL) { \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head) = NULL; \ + } \ +} while (0) + +#define HASH_OVERHEAD(hh,head) \ + (((head) != NULL) ? ( \ + (size_t)(((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ + ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ + sizeof(UT_hash_table) + \ + (HASH_BLOOM_BYTELEN))) : 0U) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#else +#define HASH_ITER(hh,head,el,tmp) \ +for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL)); \ + (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL))) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh,head) +#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1u +#define HASH_BLOOM_SIGNATURE 0xb12220f2u + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + uint8_t bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + const void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ diff --git a/ja/codes/c/utils/vector.h b/ja/codes/c/utils/vector.h new file mode 100644 index 000000000..cc51ad1f4 --- /dev/null +++ b/ja/codes/c/utils/vector.h @@ -0,0 +1,259 @@ +/** + * File: vector.h + * Created Time: 2023-07-13 + * Author: Zuoxun (845242523@qq.com)、Gonglja (glj0@outlook.com) + */ + +#ifndef VECTOR_H +#define VECTOR_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* ベクタ型を定義 */ +typedef struct vector { + int size; // 現在のベクタのサイズ + int capacity; // 現在のベクタの容量 + int depth; // 現在のベクタの深さ + void **data; // データを指すポインタ配列 +} vector; + +/* ベクタを構築 */ +vector *newVector() { + vector *v = malloc(sizeof(vector)); + v->size = 0; + v->capacity = 4; + v->depth = 1; + v->data = malloc(v->capacity * sizeof(void *)); + return v; +} + +/* ベクタを構築し、サイズと要素のデフォルト値を指定する */ +vector *_newVector(int size, void *elem, int elemSize) { + vector *v = malloc(sizeof(vector)); + v->size = size; + v->capacity = size; + v->depth = 1; + v->data = malloc(v->capacity * sizeof(void *)); + for (int i = 0; i < size; i++) { + void *tmp = malloc(sizeof(char) * elemSize); + memcpy(tmp, elem, elemSize); + v->data[i] = tmp; + } + return v; +} + +/* ベクタを破棄 */ +void delVector(vector *v) { + if (v) { + if (v->depth == 0) { + return; + } else if (v->depth == 1) { + for (int i = 0; i < v->size; i++) { + free(v->data[i]); + } + free(v); + } else { + for (int i = 0; i < v->size; i++) { + delVector(v->data[i]); + } + v->depth--; + } + } +} + +/* 要素をベクタの末尾に追加する(コピー方式) */ +void vectorPushback(vector *v, void *elem, int elemSize) { + if (v->size == v->capacity) { + v->capacity *= 2; + v->data = realloc(v->data, v->capacity * sizeof(void *)); + } + void *tmp = malloc(sizeof(char) * elemSize); + memcpy(tmp, elem, elemSize); + v->data[v->size++] = tmp; +} + +/* ベクタの末尾から要素を取り出す */ +void vectorPopback(vector *v) { + if (v->size != 0) { + free(v->data[v->size - 1]); + v->size--; + } +} + +/* ベクタをクリア */ +void vectorClear(vector *v) { + delVector(v); + v->size = 0; + v->capacity = 4; + v->depth = 1; + v->data = malloc(v->capacity * sizeof(void *)); +} + +/* ベクタのサイズを取得する */ +int vectorSize(vector *v) { + return v->size; +} + +/* ベクタの末尾要素を取得する */ +void *vectorBack(vector *v) { + int n = v->size; + return n > 0 ? v->data[n - 1] : NULL; +} + +/* ベクタの先頭要素を取得する */ +void *vectorFront(vector *v) { + return v->size > 0 ? v->data[0] : NULL; +} + +/* ベクタの添字 `pos` の要素を取得する */ +void *vectorAt(vector *v, int pos) { + if (pos < 0 || pos >= v->size) { + printf("vectorAt: out of range\n"); + return NULL; + } + return v->data[pos]; +} + +/* ベクタの添字 `pos` の要素を設定する */ +void vectorSet(vector *v, int pos, void *elem, int elemSize) { + if (pos < 0 || pos >= v->size) { + printf("vectorSet: out of range\n"); + return; + } + free(v->data[pos]); + void *tmp = malloc(sizeof(char) * elemSize); + memcpy(tmp, elem, elemSize); + v->data[pos] = tmp; +} + +/* ベクトルを拡張する */ +void vectorExpand(vector *v) { + v->capacity *= 2; + v->data = realloc(v->data, v->capacity * sizeof(void *)); +} + +/* ベクトルを縮小する */ +void vectorShrink(vector *v) { + v->capacity /= 2; + v->data = realloc(v->data, v->capacity * sizeof(void *)); +} + +/* ベクタの添字 pos に要素を挿入 */ +void vectorInsert(vector *v, int pos, void *elem, int elemSize) { + if (v->size == v->capacity) { + vectorExpand(v); + } + for (int j = v->size; j > pos; j--) { + v->data[j] = v->data[j - 1]; + } + void *tmp = malloc(sizeof(char) * elemSize); + memcpy(tmp, elem, elemSize); + v->data[pos] = tmp; + v->size++; +} + +/* ベクトルの添字 pos の要素を削除する */ +void vectorErase(vector *v, int pos) { + if (v->size != 0) { + free(v->data[pos]); + for (int j = pos; j < v->size - 1; j++) { + v->data[j] = v->data[j + 1]; + } + v->size--; + } +} + +/* ベクトルの要素を交換する */ +void vectorSwap(vector *v, int i, int j) { + void *tmp = v->data[i]; + v->data[i] = v->data[j]; + v->data[j] = tmp; +} + +/* ベクトルが空かどうか */ +bool vectorEmpty(vector *v) { + return v->size == 0; +} + +/* ベクトルが満杯かどうか */ +bool vectorFull(vector *v) { + return v->size == v->capacity; +} + +/* ベクトルが等しいかどうか */ +bool vectorEqual(vector *v1, vector *v2) { + if (v1->size != v2->size) { + printf("size not equal\n"); + return false; + } + for (int i = 0; i < v1->size; i++) { + void *a = v1->data[i]; + void *b = v2->data[i]; + if (memcmp(a, b, sizeof(a)) != 0) { + printf("data %d not equal\n", i); + return false; + } + } + return true; +} + +/* ベクタ内部をソート */ +void vectorSort(vector *v, int (*cmp)(const void *, const void *)) { + qsort(v->data, v->size, sizeof(void *), cmp); +} + +/* 出力関数。出力対象の変数を表示する関数を渡す必要がある */ +/* 現在は深さ 1 の vector のみ出力をサポート */ +void printVector(vector *v, void (*printFunc)(vector *v, void *p)) { + if (v) { + if (v->depth == 0) { + return; + } else if (v->depth == 1) { + if(v->size == 0) { + printf("\n"); + return; + } + for (int i = 0; i < v->size; i++) { + if (i == 0) { + printf("["); + } else if (i == v->size - 1) { + printFunc(v, v->data[i]); + printf("]\r\n"); + break; + } + printFunc(v, v->data[i]); + printf(","); + } + } else { + for (int i = 0; i < v->size; i++) { + printVector(v->data[i], printFunc); + } + v->depth--; + } + } +} + +/* 現在は深さ 2 の vector のみ出力をサポート */ +void printVectorMatrix(vector *vv, void (*printFunc)(vector *v, void *p)) { + printf("[\n"); + for (int i = 0; i < vv->size; i++) { + vector *v = (vector *)vv->data[i]; + printf(" ["); + for (int j = 0; j < v->size; j++) { + printFunc(v, v->data[j]); + if (j != v->size - 1) + printf(","); + } + printf("],"); + printf("\n"); + } + printf("]\n"); +} + +#ifdef __cplusplus +} +#endif + +#endif // VECTOR_H diff --git a/ja/codes/c/utils/vertex.h b/ja/codes/c/utils/vertex.h new file mode 100644 index 000000000..36ae80eea --- /dev/null +++ b/ja/codes/c/utils/vertex.h @@ -0,0 +1,49 @@ +/** + * File: vertex.h + * Created Time: 2023-10-28 + * Author: krahets (krahets@163.com) + */ + +#ifndef VERTEX_H +#define VERTEX_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* 頂点構造体 */ +typedef struct { + int val; +} Vertex; + +/* コンストラクタ。新しいノードを初期化する */ +Vertex *newVertex(int val) { + Vertex *vet; + vet = (Vertex *)malloc(sizeof(Vertex)); + vet->val = val; + return vet; +} + +/* 値の配列を頂点配列に変換 */ +Vertex **valsToVets(int *vals, int size) { + Vertex **vertices = (Vertex **)malloc(size * sizeof(Vertex *)); + for (int i = 0; i < size; ++i) { + vertices[i] = newVertex(vals[i]); + } + return vertices; +} + +/* 頂点配列を値配列に変換 */ +int *vetsToVals(Vertex **vertices, int size) { + int *vals = (int *)malloc(size * sizeof(int)); + for (int i = 0; i < size; ++i) { + vals[i] = vertices[i]->val; + } + return vals; +} + +#ifdef __cplusplus +} +#endif + +#endif // VERTEX_H diff --git a/ja/codes/cpp/.gitignore b/ja/codes/cpp/.gitignore new file mode 100644 index 000000000..dc1ffacf4 --- /dev/null +++ b/ja/codes/cpp/.gitignore @@ -0,0 +1,10 @@ +# Ignore all +* +# Unignore all with extensions +!*.* +# Unignore all dirs +!*/ + +*.dSYM/ + +build/ diff --git a/ja/codes/cpp/CMakeLists.txt b/ja/codes/cpp/CMakeLists.txt new file mode 100644 index 000000000..1e80bc4d7 --- /dev/null +++ b/ja/codes/cpp/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.10) +project(hello_algo CXX) + +set(CMAKE_CXX_STANDARD 11) + +include_directories(./include) + +add_subdirectory(chapter_computational_complexity) +add_subdirectory(chapter_array_and_linkedlist) +add_subdirectory(chapter_stack_and_queue) +add_subdirectory(chapter_hashing) +add_subdirectory(chapter_tree) +add_subdirectory(chapter_heap) +add_subdirectory(chapter_graph) +add_subdirectory(chapter_searching) +add_subdirectory(chapter_sorting) +add_subdirectory(chapter_divide_and_conquer) +add_subdirectory(chapter_backtracking) +add_subdirectory(chapter_dynamic_programming) +add_subdirectory(chapter_greedy) diff --git a/ja/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt b/ja/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt new file mode 100644 index 000000000..2e933e016 --- /dev/null +++ b/ja/codes/cpp/chapter_array_and_linkedlist/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(array array.cpp) +add_executable(linked_list linked_list.cpp) +add_executable(list list.cpp) +add_executable(my_list my_list.cpp) diff --git a/ja/codes/cpp/chapter_array_and_linkedlist/array.cpp b/ja/codes/cpp/chapter_array_and_linkedlist/array.cpp index 1bd279f1b..191c2e0cb 100644 --- a/ja/codes/cpp/chapter_array_and_linkedlist/array.cpp +++ b/ja/codes/cpp/chapter_array_and_linkedlist/array.cpp @@ -6,57 +6,57 @@ #include "../utils/common.hpp" -/* 要素への乱数アクセス */ +/* 要素へランダムアクセス */ int randomAccess(int *nums, int size) { - // [0, size)の範囲で乱数を選択 + // 区間 [0, size) からランダムに 1 つの数を選ぶ int randomIndex = rand() % size; - // 乱数要素を取得して返却 + // ランダムな要素を取得して返す int randomNum = nums[randomIndex]; return randomNum; } -/* 配列長の拡張 */ +/* 配列長を拡張する */ int *extend(int *nums, int size, int enlarge) { - // 拡張された長さの配列を初期化 + // 拡張後の長さを持つ配列を初期化する int *res = new int[size + enlarge]; // 元の配列の全要素を新しい配列にコピー for (int i = 0; i < size; i++) { res[i] = nums[i]; } - // メモリを解放 + // メモリを解放する delete[] nums; - // 拡張後の新しい配列を返却 + // 拡張後の新しい配列を返す return res; } -/* `index`に要素numを挿入 */ +/* 配列の index 番目に要素 num を挿入 */ void insert(int *nums, int size, int num, int index) { - // `index`より後のすべての要素を1つ後ろに移動 + // インデックス index 以降の全要素を 1 つ後ろへ移動する for (int i = size - 1; i > index; i--) { nums[i] = nums[i - 1]; } - // indexの位置にnumを代入 + // index の要素に num を代入する nums[index] = num; } -/* `index`の要素を削除 */ +/* index の要素を削除する */ void remove(int *nums, int size, int index) { - // `index`より後のすべての要素を1つ前に移動 + // インデックス index より後ろの全要素を 1 つ前へ移動する for (int i = index; i < size - 1; i++) { nums[i] = nums[i + 1]; } } -/* 配列の走査 */ +/* 配列を走査 */ void traverse(int *nums, int size) { int count = 0; - // インデックスによる配列の走査 + // インデックスで配列を走査 for (int i = 0; i < size; i++) { count += nums[i]; } } -/* 配列内の指定要素を検索 */ +/* 配列内で指定要素を探す */ int find(int *nums, int size, int target) { for (int i = 0; i < size; i++) { if (nums[i] == target) @@ -65,49 +65,49 @@ int find(int *nums, int size, int target) { return -1; } -/* ドライバーコード */ +/* Driver Code */ int main() { /* 配列を初期化 */ int size = 5; int *arr = new int[size]; - cout << "Array arr = "; + cout << "配列 arr = "; printArray(arr, size); int *nums = new int[size]{1, 3, 2, 5, 4}; - cout << "Array nums = "; + cout << "配列 nums = "; printArray(nums, size); - /* 乱数アクセス */ + /* ランダムアクセス */ int randomNum = randomAccess(nums, size); - cout << "Get a random element from nums = " << randomNum << endl; + cout << "nums から取得したランダム要素 " << randomNum << endl; - /* 長さの拡張 */ + /* 長さを拡張 */ int enlarge = 3; nums = extend(nums, size, enlarge); size += enlarge; - cout << "Extend the array length to 8, resulting in nums = "; + cout << "配列長を 8 に拡張し、nums = "; printArray(nums, size); - /* 要素の挿入 */ + /* 要素を挿入する */ insert(nums, size, 6, 3); - cout << "Insert the number 6 at index 3, resulting in nums = "; + cout << "インデックス 3 に数値 6 を挿入し、nums = "; printArray(nums, size); - /* 要素の削除 */ + /* 要素を削除 */ remove(nums, size, 2); - cout << "Remove the element at index 2, resulting in nums = "; + cout << "インデックス 2 の要素を削除し、nums = "; printArray(nums, size); - /* 配列の走査 */ + /* 配列を走査 */ traverse(nums, size); - /* 要素の検索 */ + /* 要素を探索する */ int index = find(nums, size, 3); - cout << "Find element 3 in nums, index = " << index << endl; + cout << "nums 内で要素 3 を検索し、インデックス = " << index << endl; - // メモリを解放 + // メモリを解放する delete[] arr; delete[] nums; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp b/ja/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp index f7ad5eb8f..6d2e7fd69 100644 --- a/ja/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp +++ b/ja/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp @@ -6,14 +6,14 @@ #include "../utils/common.hpp" -/* 連結リストのノードn0の後にノードPを挿入 */ +/* 連結リストでノード n0 の後ろにノード P を挿入する */ void insert(ListNode *n0, ListNode *P) { ListNode *n1 = n0->next; P->next = n1; n0->next = P; } -/* 連結リストのノードn0の後の最初のノードを削除 */ +/* 連結リストでノード n0 の直後のノードを削除する */ void remove(ListNode *n0) { if (n0->next == nullptr) return; @@ -21,11 +21,11 @@ void remove(ListNode *n0) { ListNode *P = n0->next; ListNode *n1 = P->next; n0->next = n1; - // メモリを解放 + // メモリを解放する delete P; } -/* 連結リストの`index`番目のノードにアクセス */ +/* 連結リスト内で index 番目のノードにアクセス */ ListNode *access(ListNode *head, int index) { for (int i = 0; i < index; i++) { if (head == nullptr) @@ -35,7 +35,7 @@ ListNode *access(ListNode *head, int index) { return head; } -/* 連結リストで値がtargetの最初のノードを検索 */ +/* 連結リストで値が target の最初のノードを探す */ int find(ListNode *head, int target) { int index = 0; while (head != nullptr) { @@ -47,7 +47,7 @@ int find(ListNode *head, int target) { return -1; } -/* ドライバーコード */ +/* Driver Code */ int main() { /* 連結リストを初期化 */ // 各ノードを初期化 @@ -56,34 +56,34 @@ int main() { ListNode *n2 = new ListNode(2); ListNode *n3 = new ListNode(5); ListNode *n4 = new ListNode(4); - // ノード間の参照を構築 + // ノード間の参照を構築する n0->next = n1; n1->next = n2; n2->next = n3; n3->next = n4; - cout << "The initialized linked list is" << endl; + cout << "初期化した連結リストは" << endl; printLinkedList(n0); /* ノードを挿入 */ insert(n0, new ListNode(0)); - cout << "Linked list after inserting the node is" << endl; + cout << "ノード挿入後の連結リストは" << endl; printLinkedList(n0); /* ノードを削除 */ remove(n0); - cout << "Linked list after removing the node is" << endl; + cout << "ノード削除後の連結リストは" << endl; printLinkedList(n0); /* ノードにアクセス */ ListNode *node = access(n0, 3); - cout << "The value of the node at index 3 in the linked list = " << node->val << endl; + cout << "連結リストのインデックス 3 のノードの値 = " << node->val << endl; - /* ノードを検索 */ + /* ノードを探索 */ int index = find(n0, 2); - cout << "The index of the node with value 2 in the linked list = " << index << endl; + cout << "連結リスト内で値が 2 のノードのインデックス = " << index << endl; - // メモリを解放 + // メモリを解放する freeMemoryLinkedList(n0); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_array_and_linkedlist/list.cpp b/ja/codes/cpp/chapter_array_and_linkedlist/list.cpp index 62ac38157..85e9792a1 100644 --- a/ja/codes/cpp/chapter_array_and_linkedlist/list.cpp +++ b/ja/codes/cpp/chapter_array_and_linkedlist/list.cpp @@ -6,25 +6,25 @@ #include "../utils/common.hpp" -/* ドライバーコード */ +/* Driver Code */ int main() { /* リストを初期化 */ vector nums = {1, 3, 2, 5, 4}; - cout << "List nums = "; + cout << "リスト nums = "; printVector(nums); /* 要素にアクセス */ int num = nums[1]; - cout << "Access the element at index 1, obtained num = " << num << endl; + cout << "インデックス 1 の要素にアクセスすると、num = " << num << endl; /* 要素を更新 */ nums[1] = 0; - cout << "Update the element at index 1 to 0, resulting in nums = "; + cout << "インデックス 1 の要素を 0 に更新すると、nums = "; printVector(nums); - /* リストをクリア */ + /* リストを空にする */ nums.clear(); - cout << "After clearing the list, nums = "; + cout << "リストを空にした後の nums = "; printVector(nums); /* 末尾に要素を追加 */ @@ -33,40 +33,40 @@ int main() { nums.push_back(2); nums.push_back(5); nums.push_back(4); - cout << "After adding elements, nums = "; + cout << "要素追加後の nums = "; printVector(nums); /* 中間に要素を挿入 */ nums.insert(nums.begin() + 3, 6); - cout << "Insert the number 6 at index 3, resulting in nums = "; + cout << "インデックス 3 に数値 6 を挿入し、nums = "; printVector(nums); /* 要素を削除 */ nums.erase(nums.begin() + 3); - cout << "Remove the element at index 3, resulting in nums = "; + cout << "インデックス 3 の要素を削除すると、nums = "; printVector(nums); - /* インデックスによるリストの走査 */ + /* インデックスでリストを走査 */ int count = 0; for (int i = 0; i < nums.size(); i++) { count += nums[i]; } - /* リスト要素の走査 */ + /* リスト要素を直接走査 */ count = 0; for (int x : nums) { count += x; } - /* 2つのリストを連結 */ + /* 2 つのリストを連結する */ vector nums1 = {6, 8, 7, 10, 9}; nums.insert(nums.end(), nums1.begin(), nums1.end()); - cout << "Concatenate list nums1 to nums, resulting in nums = "; + cout << "リスト nums1 を nums の後ろに連結すると、nums = "; printVector(nums); /* リストをソート */ sort(nums.begin(), nums.end()); - cout << "After sorting the list, nums = "; + cout << "リストをソートした後の nums = "; printVector(nums); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_array_and_linkedlist/my_list.cpp b/ja/codes/cpp/chapter_array_and_linkedlist/my_list.cpp index d294f6f63..de4e05fb0 100644 --- a/ja/codes/cpp/chapter_array_and_linkedlist/my_list.cpp +++ b/ja/codes/cpp/chapter_array_and_linkedlist/my_list.cpp @@ -10,9 +10,9 @@ class MyList { private: int *arr; // 配列(リスト要素を格納) - int arrCapacity = 10; // リストの容量 + int arrCapacity = 10; // リスト容量 int arrSize = 0; // リストの長さ(現在の要素数) - int extendRatio = 2; // リスト拡張時の倍率 + int extendRatio = 2; // リスト拡張時の増加倍率 public: /* コンストラクタ */ @@ -20,39 +20,39 @@ class MyList { arr = new int[arrCapacity]; } - /* デストラクタ */ + /* デストラクタメソッド */ ~MyList() { delete[] arr; } - /* リストの長さを取得(現在の要素数)*/ + /* リストの長さを取得(現在の要素数) */ int size() { return arrSize; } - /* リストの容量を取得 */ + /* リスト容量を取得する */ int capacity() { return arrCapacity; } /* 要素にアクセス */ int get(int index) { - // インデックスが範囲外の場合、例外をスロー(以下同様) + // インデックスが範囲外なら例外を送出する。以下同様 if (index < 0 || index >= size()) - throw out_of_range("Index out of bounds"); + throw out_of_range("インデックスが範囲外"); return arr[index]; } /* 要素を更新 */ void set(int index, int num) { if (index < 0 || index >= size()) - throw out_of_range("Index out of bounds"); + throw out_of_range("インデックスが範囲外"); arr[index] = num; } /* 末尾に要素を追加 */ void add(int num) { - // 要素数が容量を超えた場合、拡張メカニズムをトリガー + // 要素数が容量を超えると、拡張機構が発動する if (size() == capacity()) extendCapacity(); arr[size()] = num; @@ -63,11 +63,11 @@ class MyList { /* 中間に要素を挿入 */ void insert(int index, int num) { if (index < 0 || index >= size()) - throw out_of_range("Index out of bounds"); - // 要素数が容量を超えた場合、拡張メカニズムをトリガー + throw out_of_range("インデックスが範囲外"); + // 要素数が容量を超えると、拡張機構が発動する if (size() == capacity()) extendCapacity(); - // `index`より後のすべての要素を1つ後ろに移動 + // index 以降の要素をすべて 1 つ後ろへずらす for (int j = size() - 1; j >= index; j--) { arr[j + 1] = arr[j]; } @@ -79,36 +79,36 @@ class MyList { /* 要素を削除 */ int remove(int index) { if (index < 0 || index >= size()) - throw out_of_range("Index out of bounds"); + throw out_of_range("インデックスが範囲外"); int num = arr[index]; - // `index`より後のすべての要素を1つ前に移動 + // インデックス index より後の要素をすべて 1 つ前に移動する for (int j = index; j < size() - 1; j++) { arr[j] = arr[j + 1]; } // 要素数を更新 arrSize--; - // 削除された要素を返却 + // 削除された要素を返す return num; } - /* リストを拡張 */ + /* リストの拡張 */ void extendCapacity() { - // 元の配列のextendRatio倍の長さで新しい配列を作成 + // 元の配列の `extendRatio` 倍の長さを持つ新しい配列を作成する int newCapacity = capacity() * extendRatio; int *tmp = arr; arr = new int[newCapacity]; - // 元の配列のすべての要素を新しい配列にコピー + // 元の配列の全要素を新しい配列にコピー for (int i = 0; i < size(); i++) { arr[i] = tmp[i]; } - // メモリを解放 + // メモリを解放する delete[] tmp; arrCapacity = newCapacity; } - /* リストをVectorに変換して印刷用に使用 */ + /* 出力用にリストを Vector に変換 */ vector toVector() { - // 有効な長さ範囲内の要素のみを変換 + // 有効長の範囲内のリスト要素のみを変換 vector vec(size()); for (int i = 0; i < size(); i++) { vec[i] = arr[i]; @@ -117,7 +117,7 @@ class MyList { } }; -/* ドライバーコード */ +/* Driver Code */ int main() { /* リストを初期化 */ MyList *nums = new MyList(); @@ -127,45 +127,45 @@ int main() { nums->add(2); nums->add(5); nums->add(4); - cout << "List nums = "; + cout << "リスト nums = "; vector vec = nums->toVector(); printVector(vec); - cout << "Capacity = " << nums->capacity() << ", length = " << nums->size() << endl; + cout << "容量 = " << nums->capacity() << " ,長さ = " << nums->size() << endl; /* 中間に要素を挿入 */ nums->insert(3, 6); - cout << "Insert the number 6 at index 3, resulting in nums = "; + cout << "インデックス 3 に数値 6 を挿入し、nums = "; vec = nums->toVector(); printVector(vec); /* 要素を削除 */ nums->remove(3); - cout << "Remove the element at index 3, resulting in nums = "; + cout << "インデックス 3 の要素を削除すると、nums = "; vec = nums->toVector(); printVector(vec); /* 要素にアクセス */ int num = nums->get(1); - cout << "Access the element at index 1, obtained num = " << num << endl; + cout << "インデックス 1 の要素にアクセスすると、num = " << num << endl; /* 要素を更新 */ nums->set(1, 0); - cout << "Update the element at index 1 to 0, resulting in nums = "; + cout << "インデックス 1 の要素を 0 に更新すると、nums = "; vec = nums->toVector(); printVector(vec); - /* 拡張メカニズムをテスト */ + /* 拡張機構をテストする */ for (int i = 0; i < 10; i++) { - // i = 5の時、リストの長さがリストの容量を超え、この時点で拡張メカニズムがトリガーされる + // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する nums->add(i); } - cout << "After extending, list nums = "; + cout << "拡張後のリスト nums = "; vec = nums->toVector(); printVector(vec); - cout << "Capacity = " << nums->capacity() << ", length = " << nums->size() << endl; + cout << "容量 = " << nums->capacity() << " ,長さ = " << nums->size() << endl; - // メモリを解放 + // メモリを解放する delete nums; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_backtracking/CMakeLists.txt b/ja/codes/cpp/chapter_backtracking/CMakeLists.txt new file mode 100644 index 000000000..6c271e330 --- /dev/null +++ b/ja/codes/cpp/chapter_backtracking/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(preorder_traversal_i_compact preorder_traversal_i_compact.cpp) +add_executable(preorder_traversal_ii_compact preorder_traversal_ii_compact.cpp) +add_executable(preorder_traversal_iii_compact preorder_traversal_iii_compact.cpp) +add_executable(preorder_traversal_iii_template preorder_traversal_iii_template.cpp) +add_executable(permutations_i permutations_i.cpp) +add_executable(permutations_ii permutations_ii.cpp) +add_executable(n_queens n_queens.cpp) +add_executable(subset_sum_i_naive subset_sum_i_naive.cpp) +add_executable(subset_sum_i subset_sum_i.cpp) +add_executable(subset_sum_ii subset_sum_ii.cpp) diff --git a/ja/codes/cpp/chapter_backtracking/n_queens.cpp b/ja/codes/cpp/chapter_backtracking/n_queens.cpp index de89dc413..d7631cdee 100644 --- a/ja/codes/cpp/chapter_backtracking/n_queens.cpp +++ b/ja/codes/cpp/chapter_backtracking/n_queens.cpp @@ -6,40 +6,40 @@ #include "../utils/common.hpp" -/* バックトラッキングアルゴリズム:n クイーン */ +/* バックトラッキング:N クイーン */ void backtrack(int row, int n, vector> &state, vector>> &res, vector &cols, vector &diags1, vector &diags2) { - // すべての行が配置されたら、解を記録 + // すべての行への配置が完了したら、解を記録する if (row == n) { res.push_back(state); return; } // すべての列を走査 for (int col = 0; col < n; col++) { - // セルに対応する主対角線と副対角線を計算 + // このマスに対応する主対角線と副対角線を計算 int diag1 = row - col + n - 1; int 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; } } } -/* n クイーンを解く */ +/* N クイーンを解く */ vector>> nQueens(int n) { - // n*n サイズのチェスボードを初期化、'Q' はクイーンを表し、'#' は空のスポットを表す + // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す vector> state(n, vector(n, "#")); - vector cols(n, false); // クイーンのある列を記録 - vector diags1(2 * n - 1, false); // クイーンのある主対角線を記録 - vector diags2(2 * n - 1, false); // クイーンのある副対角線を記録 + vector cols(n, false); // 列にクイーンがあるか記録 + vector diags1(2 * n - 1, false); // 主対角線にクイーンがあるかを記録 + vector diags2(2 * n - 1, false); // 副対角線にクイーンがあるかを記録 vector>> res; backtrack(0, n, state, res, cols, diags1, diags2); @@ -47,13 +47,13 @@ vector>> nQueens(int n) { return res; } -/* ドライバーコード */ +/* Driver Code */ int main() { int n = 4; vector>> res = nQueens(n); - cout << "チェスボードの次元を " << n << " として入力" << endl; - cout << "クイーン配置解の総数 = " << res.size() << endl; + cout << "入力した盤面の縦横は " << n << endl; + cout << "クイーンの配置方法は全部で " << res.size() << " 通り" << endl; for (const vector> &state : res) { cout << "--------------------" << endl; for (const vector &row : state) { @@ -62,4 +62,4 @@ int main() { } return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_backtracking/permutations_i.cpp b/ja/codes/cpp/chapter_backtracking/permutations_i.cpp index 2d637e71f..b32626c84 100644 --- a/ja/codes/cpp/chapter_backtracking/permutations_i.cpp +++ b/ja/codes/cpp/chapter_backtracking/permutations_i.cpp @@ -6,9 +6,9 @@ #include "../utils/common.hpp" -/* バックトラッキングアルゴリズム:順列 I */ +/* バックトラッキング:順列 I */ void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { - // 状態の長さが要素数と等しくなったら、解を記録 + // 状態の長さが要素数に等しければ、解を記録 if (state.size() == choices.size()) { res.push_back(state); return; @@ -16,21 +16,21 @@ void backtrack(vector &state, const vector &choices, vector &sel // すべての選択肢を走査 for (int i = 0; i < choices.size(); i++) { int choice = choices[i]; - // 剪定:要素の重複選択を許可しない + // 枝刈り:要素の重複選択を許可しない if (!selected[i]) { - // 試行:選択を行い、状態を更新 + // 試行: 選択を行い、状態を更新 selected[i] = true; state.push_back(choice); - // 次のラウンドの選択に進む + // 次の選択へ進む backtrack(state, choices, selected, res); - // 回退:選択を取り消し、前の状態に復元 + // バックトラック:選択を取り消し、前の状態に戻す selected[i] = false; state.pop_back(); } } } -/* 順列 I */ +/* 全順列 I */ vector> permutationsI(vector nums) { vector state; vector selected(nums.size(), false); @@ -39,7 +39,7 @@ vector> permutationsI(vector nums) { return res; } -/* ドライバーコード */ +/* Driver Code */ int main() { vector nums = {1, 2, 3}; @@ -51,4 +51,4 @@ int main() { printVectorMatrix(res); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_backtracking/permutations_ii.cpp b/ja/codes/cpp/chapter_backtracking/permutations_ii.cpp index 0f795da1a..b65b2d882 100644 --- a/ja/codes/cpp/chapter_backtracking/permutations_ii.cpp +++ b/ja/codes/cpp/chapter_backtracking/permutations_ii.cpp @@ -6,9 +6,9 @@ #include "../utils/common.hpp" -/* バックトラッキングアルゴリズム:順列 II */ +/* バックトラッキング:順列 II */ void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { - // 状態の長さが要素数と等しくなったら、解を記録 + // 状態の長さが要素数に等しければ、解を記録 if (state.size() == choices.size()) { res.push_back(state); return; @@ -17,22 +17,22 @@ void backtrack(vector &state, const vector &choices, vector &sel unordered_set duplicated; for (int i = 0; i < choices.size(); i++) { int choice = choices[i]; - // 剪定:要素の重複選択を許可せず、等しい要素の重複選択も許可しない + // 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない if (!selected[i] && duplicated.find(choice) == duplicated.end()) { - // 試行:選択を行い、状態を更新 - duplicated.emplace(choice); // 選択された要素値を記録 + // 試行: 選択を行い、状態を更新 + duplicated.emplace(choice); // 選択済みの要素値を記録 selected[i] = true; state.push_back(choice); - // 次のラウンドの選択に進む + // 次の選択へ進む backtrack(state, choices, selected, res); - // 回退:選択を取り消し、前の状態に復元 + // バックトラック:選択を取り消し、前の状態に戻す selected[i] = false; state.pop_back(); } } } -/* 順列 II */ +/* 全順列 II */ vector> permutationsII(vector nums) { vector state; vector selected(nums.size(), false); @@ -41,7 +41,7 @@ vector> permutationsII(vector nums) { return res; } -/* ドライバーコード */ +/* Driver Code */ int main() { vector nums = {1, 1, 2}; @@ -53,4 +53,4 @@ int main() { printVectorMatrix(res); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp b/ja/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp index 841d2b7e6..8617c4904 100644 --- a/ja/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp +++ b/ja/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp @@ -8,7 +8,7 @@ vector res; -/* 前順走査:例1 */ +/* 前順走査:例題 1 */ void preOrder(TreeNode *root) { if (root == nullptr) { return; @@ -21,23 +21,19 @@ void preOrder(TreeNode *root) { preOrder(root->right); } -/* ドライバーコード */ +/* Driver Code */ int main() { - vector arr = {1, 7, 3, 4, 5, 6, 7}; - TreeNode *root = vecToTree(arr); + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\n二分木を初期化" << endl; printTree(root); - // 前順走査 - res.clear(); + // 先行順走査 preOrder(root); - cout << "\n値7のノードをすべて出力" << endl; + cout << "\n値が 7 のすべてのノードを出力" << endl; vector vals; for (TreeNode *node : res) { vals.push_back(node->val); } printVector(vals); - - return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp b/ja/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp index 7a34f6d94..837c267be 100644 --- a/ja/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp +++ b/ja/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp @@ -9,12 +9,12 @@ vector path; vector> res; -/* 前順走査:例2 */ +/* 前順走査:例題 2 */ void preOrder(TreeNode *root) { if (root == nullptr) { return; } - // 試行 + // 試す path.push_back(root); if (root->val == 7) { // 解を記録 @@ -22,23 +22,20 @@ void preOrder(TreeNode *root) { } preOrder(root->left); preOrder(root->right); - // 回退 + // バックトラック path.pop_back(); } -/* ドライバーコード */ +/* Driver Code */ int main() { - vector arr = {1, 7, 3, 4, 5, 6, 7}; - TreeNode *root = vecToTree(arr); + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\n二分木を初期化" << endl; printTree(root); - // 前順走査 - path.clear(); - res.clear(); + // 先行順走査 preOrder(root); - cout << "\nルートからノード7までのすべてのパスを出力" << endl; + cout << "\n根ノードからノード 7 までのすべての経路を出力" << endl; for (vector &path : res) { vector vals; for (TreeNode *node : path) { @@ -46,6 +43,4 @@ int main() { } printVector(vals); } - - return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp b/ja/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp index 0de63144b..0ccc104fb 100644 --- a/ja/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp +++ b/ja/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp @@ -9,13 +9,13 @@ vector path; vector> res; -/* 前順走査:例3 */ +/* 前順走査:例題 3 */ void preOrder(TreeNode *root) { - // 剪定 + // 枝刈り if (root == nullptr || root->val == 3) { return; } - // 試行 + // 試す path.push_back(root); if (root->val == 7) { // 解を記録 @@ -23,23 +23,20 @@ void preOrder(TreeNode *root) { } preOrder(root->left); preOrder(root->right); - // 回退 + // バックトラック path.pop_back(); } -/* ドライバーコード */ +/* Driver Code */ int main() { - vector arr = {1, 7, 3, 4, 5, 6, 7}; - TreeNode *root = vecToTree(arr); + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\n二分木を初期化" << endl; printTree(root); - // 前順走査 - path.clear(); - res.clear(); + // 先行順走査 preOrder(root); - cout << "\nルートからノード7までのすべてのパスを出力、値3のノードは含まない" << endl; + cout << "\n根ノードからノード 7 までのすべての経路を出力し、経路に値 3 のノードを含めない" << endl; for (vector &path : res) { vector vals; for (TreeNode *node : path) { @@ -47,6 +44,4 @@ int main() { } printVector(vals); } - - return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp b/ja/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp index 8c6f976ee..2156714c0 100644 --- a/ja/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp +++ b/ja/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp @@ -16,7 +16,7 @@ void recordSolution(vector &state, vector> &res) res.push_back(state); } -/* 現在の状態下で選択が合法かどうかを判定 */ +/* 現在の状態で、この選択が有効かどうかを判定 */ bool isValid(vector &state, TreeNode *choice) { return choice != nullptr && choice->val != 3; } @@ -26,47 +26,46 @@ void makeChoice(vector &state, TreeNode *choice) { state.push_back(choice); } -/* 状態を復元 */ +/* 状態を元に戻す */ void undoChoice(vector &state, TreeNode *choice) { state.pop_back(); } -/* バックトラッキングアルゴリズム:例3 */ +/* バックトラッキング:例題 3 */ void backtrack(vector &state, vector &choices, vector> &res) { - // 解かどうかをチェック + // 解かどうかを確認 if (isSolution(state)) { // 解を記録 recordSolution(state, res); } // すべての選択肢を走査 for (TreeNode *choice : choices) { - // 剪定:選択が合法かどうかをチェック + // 枝刈り:選択が妥当かを確認する if (isValid(state, choice)) { - // 試行:選択を行い、状態を更新 + // 試行: 選択を行い、状態を更新 makeChoice(state, choice); - // 次のラウンドの選択に進む + // 次の選択へ進む vector nextChoices{choice->left, choice->right}; backtrack(state, nextChoices, res); - // 回退:選択を取り消し、前の状態に復元 + // バックトラック:選択を取り消し、前の状態に戻す undoChoice(state, choice); } } } -/* ドライバーコード */ +/* Driver Code */ int main() { - vector arr = {1, 7, 3, 4, 5, 6, 7}; - TreeNode *root = vecToTree(arr); + TreeNode *root = vectorToTree(vector{1, 7, 3, 4, 5, 6, 7}); cout << "\n二分木を初期化" << endl; printTree(root); - // バックトラッキングアルゴリズム + // バックトラッキング法 vector state; vector choices = {root}; vector> res; backtrack(state, choices, res); - cout << "\nルートからノード7までのすべてのパスを出力、パスには値3のノードを含まないことが要求される" << endl; + cout << "\n根ノードからノード 7 までのすべての経路を出力し、経路に値 3 のノードを含めない" << endl; for (vector &path : res) { vector vals; for (TreeNode *node : path) { @@ -74,6 +73,4 @@ int main() { } printVector(vals); } - - return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_backtracking/subset_sum_i.cpp b/ja/codes/cpp/chapter_backtracking/subset_sum_i.cpp index 72c49f4df..5e3751bce 100644 --- a/ja/codes/cpp/chapter_backtracking/subset_sum_i.cpp +++ b/ja/codes/cpp/chapter_backtracking/subset_sum_i.cpp @@ -6,41 +6,41 @@ #include "../utils/common.hpp" -/* バックトラッキングアルゴリズム:部分集合和 I */ +/* バックトラッキング:部分和 I */ void backtrack(vector &state, int target, vector &choices, int start, vector> &res) { - // 部分集合の和がtargetと等しいとき、解を記録 + // 部分集合の和が target に等しければ、解を記録 if (target == 0) { res.push_back(state); return; } // すべての選択肢を走査 - // 剪定二:startから走査を開始し、重複する部分集合の生成を回避 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける for (int i = start; i < choices.size(); i++) { - // 剪定一:部分集合の和がtargetを超えた場合、即座にループを終了 - // 配列がソートされているため、後の要素はさらに大きく、部分集合の和は必ずtargetを超える + // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if (target - choices[i] < 0) { break; } - // 試行:選択を行い、target、startを更新 + // 試す:選択を行い、target と start を更新 state.push_back(choices[i]); - // 次のラウンドの選択に進む + // 次の選択へ進む backtrack(state, target - choices[i], choices, i, res); - // 回退:選択を取り消し、前の状態に復元 + // バックトラック:選択を取り消し、前の状態に戻す state.pop_back(); } } -/* 部分集合和 I を解く */ -vector> subsetSumI(vector nums, int target) { +/* 部分和 I を解く */ +vector> subsetSumI(vector &nums, int target) { vector state; // 状態(部分集合) sort(nums.begin(), nums.end()); // nums をソート - int start = 0; // 走査の開始点 - vector> res; // 結果リスト(部分集合リスト) + int start = 0; // 開始点を走査 + vector> res; // 結果リスト(部分集合のリスト) backtrack(state, target, nums, start, res); return res; } -/* ドライバーコード */ +/* Driver Code */ int main() { vector nums = {3, 4, 5}; int target = 9; @@ -50,8 +50,8 @@ int main() { cout << "入力配列 nums = "; printVector(nums); cout << "target = " << target << endl; - cout << "和が " << target << " のすべての部分集合 res = " << endl; + cout << "合計が " << target << " に等しいすべての部分集合 res = " << endl; printVectorMatrix(res); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp b/ja/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp index 8fcf2b7cc..c6b1b9a8f 100644 --- a/ja/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp +++ b/ja/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp @@ -6,38 +6,38 @@ #include "../utils/common.hpp" -/* バックトラッキングアルゴリズム:部分集合和 I */ +/* バックトラッキング:部分和 I */ void backtrack(vector &state, int target, int total, vector &choices, vector> &res) { - // 部分集合の和がtargetと等しいとき、解を記録 + // 部分集合の和が target に等しければ、解を記録 if (total == target) { res.push_back(state); return; } // すべての選択肢を走査 - for (int i = 0; i < choices.size(); i++) { - // 剪定:部分集合の和がtargetを超えた場合、その選択をスキップ + for (size_t i = 0; i < choices.size(); i++) { + // 枝刈り:部分和が target を超える場合はその選択をスキップする if (total + choices[i] > target) { continue; } - // 試行:選択を行い、要素とtotalを更新 + // 試行:選択を行い、要素と total を更新する state.push_back(choices[i]); - // 次のラウンドの選択に進む + // 次の選択へ進む backtrack(state, target, total + choices[i], choices, res); - // 回退:選択を取り消し、前の状態に復元 + // バックトラック:選択を取り消し、前の状態に戻す state.pop_back(); } } -/* 部分集合和 I を解く(重複する部分集合を含む) */ -vector> subsetSumINaive(vector nums, int target) { +/* 部分和 I を解く(重複部分集合を含む) */ +vector> subsetSumINaive(vector &nums, int target) { vector state; // 状態(部分集合) - int total = 0; // 部分集合の和 - vector> res; // 結果リスト(部分集合リスト) + int total = 0; // 部分和 + vector> res; // 結果リスト(部分集合のリスト) backtrack(state, target, total, nums, res); return res; } -/* ドライバーコード */ +/* Driver Code */ int main() { vector nums = {3, 4, 5}; int target = 9; @@ -47,9 +47,8 @@ int main() { cout << "入力配列 nums = "; printVector(nums); cout << "target = " << target << endl; - cout << "和が " << target << " のすべての部分集合 res = " << endl; + cout << "合計が " << target << " に等しいすべての部分集合 res = " << endl; printVectorMatrix(res); - cout << "この方法の結果には重複する集合が含まれています" << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_backtracking/subset_sum_ii.cpp b/ja/codes/cpp/chapter_backtracking/subset_sum_ii.cpp index 4649d51b2..7fc601779 100644 --- a/ja/codes/cpp/chapter_backtracking/subset_sum_ii.cpp +++ b/ja/codes/cpp/chapter_backtracking/subset_sum_ii.cpp @@ -6,46 +6,46 @@ #include "../utils/common.hpp" -/* バックトラッキングアルゴリズム:部分集合和 II */ +/* バックトラッキング:部分和 II */ void backtrack(vector &state, int target, vector &choices, int start, vector> &res) { - // 部分集合の和がtargetと等しいとき、解を記録 + // 部分集合の和が target に等しければ、解を記録 if (target == 0) { res.push_back(state); return; } // すべての選択肢を走査 - // 剪定二:startから走査を開始し、重複する部分集合の生成を回避 - // 剪定三:startから走査を開始し、同じ要素の繰り返し選択を回避 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける for (int i = start; i < choices.size(); i++) { - // 剪定一:部分集合の和がtargetを超えた場合、即座にループを終了 - // 配列がソートされているため、後の要素はさらに大きく、部分集合の和は必ずtargetを超える + // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if (target - choices[i] < 0) { break; } - // 剪定四:要素が左の要素と等しい場合、検索ブランチの重複を示すのでスキップ + // 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする if (i > start && choices[i] == choices[i - 1]) { continue; } - // 試行:選択を行い、target、startを更新 + // 試す:選択を行い、target と start を更新 state.push_back(choices[i]); - // 次のラウンドの選択に進む + // 次の選択へ進む backtrack(state, target - choices[i], choices, i + 1, res); - // 回退:選択を取り消し、前の状態に復元 + // バックトラック:選択を取り消し、前の状態に戻す state.pop_back(); } } -/* 部分集合和 II を解く */ -vector> subsetSumII(vector nums, int target) { +/* 部分和 II を解く */ +vector> subsetSumII(vector &nums, int target) { vector state; // 状態(部分集合) sort(nums.begin(), nums.end()); // nums をソート - int start = 0; // 走査の開始点 - vector> res; // 結果リスト(部分集合リスト) + int start = 0; // 開始点を走査 + vector> res; // 結果リスト(部分集合のリスト) backtrack(state, target, nums, start, res); return res; } -/* ドライバーコード */ +/* Driver Code */ int main() { vector nums = {4, 4, 5}; int target = 9; @@ -55,8 +55,8 @@ int main() { cout << "入力配列 nums = "; printVector(nums); cout << "target = " << target << endl; - cout << "和が " << target << " のすべての部分集合 res = " << endl; + cout << "合計が " << target << " に等しいすべての部分集合 res = " << endl; printVectorMatrix(res); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_computational_complexity/CMakeLists.txt b/ja/codes/cpp/chapter_computational_complexity/CMakeLists.txt new file mode 100644 index 000000000..ea2845b75 --- /dev/null +++ b/ja/codes/cpp/chapter_computational_complexity/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(iteration iteration.cpp) +add_executable(recursion recursion.cpp) +add_executable(space_complexity space_complexity.cpp) +add_executable(time_complexity time_complexity.cpp) +add_executable(worst_best_time_complexity worst_best_time_complexity.cpp) \ No newline at end of file diff --git a/ja/codes/cpp/chapter_computational_complexity/iteration.cpp b/ja/codes/cpp/chapter_computational_complexity/iteration.cpp index 4ad0f399b..35a4ec2ec 100644 --- a/ja/codes/cpp/chapter_computational_complexity/iteration.cpp +++ b/ja/codes/cpp/chapter_computational_complexity/iteration.cpp @@ -9,7 +9,7 @@ /* for ループ */ int forLoop(int n) { int res = 0; - // 1, 2, ..., n-1, n の合計をループ計算 + // 1, 2, ..., n-1, n を順に加算する for (int i = 1; i <= n; ++i) { res += i; } @@ -19,35 +19,35 @@ int forLoop(int n) { /* while ループ */ int whileLoop(int n) { int res = 0; - int i = 1; // 条件変数を初期化 - // 1, 2, ..., n-1, n の合計をループ計算 + int i = 1; // 条件変数を初期化する + // 1, 2, ..., n-1, n を順に加算する while (i <= n) { res += i; - i++; // 条件変数を更新 + i++; // 条件変数を更新する } return res; } -/* while ループ(2つの更新) */ +/* while ループ(2回更新) */ int whileLoopII(int n) { int res = 0; - int i = 1; // 条件変数を初期化 - // 1, 4, 10, ... の合計をループ計算 + int i = 1; // 条件変数を初期化する + // 1, 4, 10, ... を順に加算する while (i <= n) { res += i; - // 条件変数を更新 + // 条件変数を更新する i++; i *= 2; } return res; } -/* 2重 for ループ */ +/* 二重 for ループ */ string nestedForLoop(int n) { ostringstream res; - // ループ i = 1, 2, ..., n-1, n + // i = 1, 2, ..., n-1, n とループする for (int i = 1; i <= n; ++i) { - // ループ j = 1, 2, ..., n-1, n + // j = 1, 2, ..., n-1, n とループする for (int j = 1; j <= n; ++j) { res << "(" << i << ", " << j << "), "; } @@ -55,7 +55,7 @@ string nestedForLoop(int n) { return res.str(); } -/* ドライバーコード */ +/* Driver Code */ int main() { int n = 5; int res; @@ -67,10 +67,10 @@ int main() { cout << "\nwhile ループの合計結果 res = " << res << endl; res = whileLoopII(n); - cout << "\nwhile ループ(2つの更新)の合計結果 res = " << res << endl; + cout << "\nwhile ループ(2 回更新)の合計結果 res = " << res << endl; string resStr = nestedForLoop(n); - cout << "\n2重 for ループ走査の結果 = " << resStr << endl; + cout << "\n二重 for ループの走査結果 " << resStr << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_computational_complexity/recursion.cpp b/ja/codes/cpp/chapter_computational_complexity/recursion.cpp index a481f89e8..c3fc5d313 100644 --- a/ja/codes/cpp/chapter_computational_complexity/recursion.cpp +++ b/ja/codes/cpp/chapter_computational_complexity/recursion.cpp @@ -13,23 +13,23 @@ int recur(int n) { return 1; // 再帰:再帰呼び出し int res = recur(n - 1); - // 戻り値:結果を返す + // 帰りがけ:結果を返す return n + res; } -/* 反復で再帰をシミュレート */ +/* 反復で再帰を模擬する */ int forLoopRecur(int n) { - // 明示的なスタックを使用してシステムコールスタックをシミュレート + // 明示的なスタックを使ってシステムコールスタックを模擬する stack stack; int res = 0; // 再帰:再帰呼び出し for (int i = n; i > 0; i--) { - // 「スタックへのプッシュ」で「再帰」をシミュレート + // 「スタックへのプッシュ」で「再帰」を模擬する stack.push(i); } - // 戻り値:結果を返す + // 帰りがけ:結果を返す while (!stack.empty()) { - // 「スタックからのポップ」で「戻り値」をシミュレート + // 「スタックから取り出す操作」で「帰り」をシミュレート res += stack.top(); stack.pop(); } @@ -51,13 +51,13 @@ int fib(int n) { // 終了条件 f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; - // 再帰呼び出し f(n) = f(n-1) + f(n-2) + // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す int res = fib(n - 1) + fib(n - 2); // 結果 f(n) を返す return res; } -/* ドライバーコード */ +/* Driver Code */ int main() { int n = 5; int res; @@ -66,13 +66,13 @@ int main() { cout << "\n再帰関数の合計結果 res = " << res << endl; res = forLoopRecur(n); - cout << "\n反復を使用して再帰をシミュレートした合計結果 res = " << res << endl; + cout << "\n反復で再帰をシミュレートした合計結果 res = " << res << endl; res = tailRecur(n, 0); cout << "\n末尾再帰関数の合計結果 res = " << res << endl; res = fib(n); - cout << "フィボナッチ数列の第 " << n << " 番目の数は " << res << endl; + cout << "\nフィボナッチ数列の第 " << n << " 項は " << res << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_computational_complexity/space_complexity.cpp b/ja/codes/cpp/chapter_computational_complexity/space_complexity.cpp index 2d61b66c8..7096506fc 100644 --- a/ja/codes/cpp/chapter_computational_complexity/space_complexity.cpp +++ b/ja/codes/cpp/chapter_computational_complexity/space_complexity.cpp @@ -8,44 +8,44 @@ /* 関数 */ int func() { - // 何らかの操作を実行 + // 何らかの処理を行う return 0; } -/* 定数計算量 */ +/* 定数階 */ void constant(int n) { - // 定数、変数、オブジェクトは O(1) 空間を占める + // 定数、変数、オブジェクトは O(1) の空間を占める const int a = 0; int b = 0; vector nums(10000); ListNode node(0); - // ループ内の変数は O(1) 空間を占める + // ループ内の変数は O(1) の空間を占める for (int i = 0; i < n; i++) { int c = 0; } - // ループ内の関数は O(1) 空間を占める + // ループ内の関数は O(1) の空間を占める for (int i = 0; i < n; i++) { func(); } } -/* 線形計算量 */ +/* 線形階 */ void linear(int n) { - // 長さ n の配列は O(n) 空間を占める + // 長さ n の配列は O(n) の空間を使用 vector nums(n); - // 長さ n のリストは O(n) 空間を占める + // 長さ n のリストは O(n) の空間を使用 vector nodes; for (int i = 0; i < n; i++) { nodes.push_back(ListNode(i)); } - // 長さ n のハッシュテーブルは O(n) 空間を占める + // 長さ n のハッシュテーブルは O(n) の空間を使用 unordered_map map; for (int i = 0; i < n; i++) { map[i] = to_string(i); } } -/* 線形計算量(再帰実装) */ +/* 線形時間(再帰実装) */ void linearRecur(int n) { cout << "再帰 n = " << n << endl; if (n == 1) @@ -53,9 +53,9 @@ void linearRecur(int n) { linearRecur(n - 1); } -/* 二次計算量 */ +/* 二乗階 */ void quadratic(int n) { - // 二次元リストは O(n^2) 空間を占める + // 二次元リストは O(n^2) の空間を使用 vector> numMatrix; for (int i = 0; i < n; i++) { vector tmp; @@ -66,16 +66,16 @@ void quadratic(int n) { } } -/* 二次計算量(再帰実装) */ +/* 二次時間(再帰実装) */ int quadraticRecur(int n) { if (n <= 0) return 0; vector nums(n); - cout << "再帰 n = " << n << ", nums の長さ = " << nums.size() << endl; + cout << "再帰 n = " << n << " における nums の長さ = " << nums.size() << endl; return quadraticRecur(n - 1); } -/* 指数計算量(完全二分木の構築) */ +/* 指数時間(完全二分木の構築) */ TreeNode *buildTree(int n) { if (n == 0) return nullptr; @@ -85,23 +85,23 @@ TreeNode *buildTree(int n) { return root; } -/* ドライバーコード */ +/* Driver Code */ int main() { int n = 5; - // 定数計算量 + // 定数階 constant(n); - // 線形計算量 + // 線形階 linear(n); linearRecur(n); - // 二次計算量 + // 二乗階 quadratic(n); quadraticRecur(n); - // 指数計算量 + // 指数オーダー TreeNode *root = buildTree(n); printTree(root); - // メモリを解放 + // メモリを解放する freeMemoryTree(root); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_computational_complexity/time_complexity.cpp b/ja/codes/cpp/chapter_computational_complexity/time_complexity.cpp index c20257b8e..fb3fa1820 100644 --- a/ja/codes/cpp/chapter_computational_complexity/time_complexity.cpp +++ b/ja/codes/cpp/chapter_computational_complexity/time_complexity.cpp @@ -6,7 +6,7 @@ #include "../utils/common.hpp" -/* 定数計算量 */ +/* 定数階 */ int constant(int n) { int count = 0; int size = 100000; @@ -15,7 +15,7 @@ int constant(int n) { return count; } -/* 線形計算量 */ +/* 線形階 */ int linear(int n) { int count = 0; for (int i = 0; i < n; i++) @@ -23,20 +23,20 @@ int linear(int n) { return count; } -/* 線形計算量(配列の走査) */ +/* 線形時間(配列を走査) */ int arrayTraversal(vector &nums) { int count = 0; - // ループ回数は配列の長さに比例 + // ループ回数は配列長に比例する for (int num : nums) { count++; } return count; } -/* 二次計算量 */ +/* 二乗階 */ int quadratic(int n) { int count = 0; - // ループ回数はデータサイズ n の二乗に比例 + // ループ回数はデータサイズ n の二乗に比例する for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; @@ -45,29 +45,29 @@ int quadratic(int n) { return count; } -/* 二次計算量(バブルソート) */ +/* 二次時間(バブルソート) */ int bubbleSort(vector &nums) { - int count = 0; // カウンター - // 外側ループ:未ソート範囲は [0, i] + int count = 0; // カウンタ + // 外側のループ:未ソート区間は [0, i] for (int i = nums.size() - 1; i > 0; i--) { - // 内側ループ:未ソート範囲 [0, i] の最大要素を範囲の右端にスワップ + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { - // nums[j] と nums[j + 1] をスワップ + // nums[j] と nums[j + 1] を交換 int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; - count += 3; // 要素のスワップには3つの個別操作が含まれる + count += 3; // 要素交換には 3 回の単位操作が含まれる } } } return count; } -/* 指数計算量(ループ実装) */ +/* 指数時間(ループ実装) */ int exponential(int n) { int count = 0, base = 1; - // セルは毎ラウンド2つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成 + // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する for (int i = 0; i < n; i++) { for (int j = 0; j < base; j++) { count++; @@ -78,14 +78,14 @@ int exponential(int n) { return count; } -/* 指数計算量(再帰実装) */ +/* 指数時間(再帰実装) */ int expRecur(int n) { if (n == 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } -/* 対数計算量(ループ実装) */ +/* 対数時間(ループ実装) */ int logarithmic(int n) { int count = 0; while (n > 1) { @@ -95,14 +95,14 @@ int logarithmic(int n) { return count; } -/* 対数計算量(再帰実装) */ +/* 対数時間(再帰実装) */ int logRecur(int n) { if (n <= 1) return 0; return logRecur(n / 2) + 1; } -/* 線形対数計算量 */ +/* 線形対数時間 */ int linearLogRecur(int n) { if (n <= 1) return 1; @@ -113,56 +113,56 @@ int linearLogRecur(int n) { return count; } -/* 階乗計算量(再帰実装) */ +/* 階乗時間(再帰実装) */ int factorialRecur(int n) { if (n == 0) return 1; int count = 0; - // 1から n に分裂 + // 1個から n 個に分裂 for (int i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } -/* ドライバーコード */ +/* Driver Code */ int main() { - // n を変更して、さまざまな計算量での操作回数の変化傾向を体験可能 + // n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる int n = 8; cout << "入力データサイズ n = " << n << endl; int count = constant(n); - cout << "定数計算量の操作回数 = " << count << endl; + cout << "定数オーダーの操作回数 = " << count << endl; count = linear(n); - cout << "線形計算量の操作回数 = " << count << endl; + cout << "線形オーダーの操作回数 = " << count << endl; vector arr(n); count = arrayTraversal(arr); - cout << "線形計算量の操作回数(配列走査) = " << count << endl; + cout << "線形オーダー(配列走査)の操作回数 = " << count << endl; count = quadratic(n); - cout << "二次計算量の操作回数 = " << count << endl; + cout << "二乗オーダーの操作回数 = " << count << endl; vector nums(n); for (int i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] count = bubbleSort(nums); - cout << "二次計算量の操作回数(バブルソート) = " << count << endl; + cout << "二乗オーダー(バブルソート)の操作回数 = " << count << endl; count = exponential(n); - cout << "指数計算量の操作回数(ループ実装) = " << count << endl; + cout << "指数オーダー(ループ実装)の操作回数 = " << count << endl; count = expRecur(n); - cout << "指数計算量の操作回数(再帰実装) = " << count << endl; + cout << "指数オーダー(再帰実装)の操作回数 = " << count << endl; count = logarithmic(n); - cout << "対数計算量の操作回数(ループ実装) = " << count << endl; + cout << "対数オーダー(ループ実装)の操作回数 = " << count << endl; count = logRecur(n); - cout << "対数計算量の操作回数(再帰実装) = " << count << endl; + cout << "対数オーダー(再帰実装)の操作回数 = " << count << endl; count = linearLogRecur(n); - cout << "線形対数計算量の操作回数(再帰実装) = " << count << endl; + cout << "線形対数オーダー(再帰実装)の操作回数 = " << count << endl; count = factorialRecur(n); - cout << "階乗計算量の操作回数(再帰実装) = " << count << endl; + cout << "階乗オーダー(再帰実装)の操作回数 = " << count << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp b/ja/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp index 517a610be..3428cbac5 100644 --- a/ja/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp +++ b/ja/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp @@ -6,40 +6,40 @@ #include "../utils/common.hpp" -/* 要素 {1, 2, ..., n} をランダムにシャッフルした配列を生成 */ +/* 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 */ vector randomNumbers(int n) { vector nums(n); // 配列 nums = { 1, 2, 3, ..., n } を生成 for (int i = 0; i < n; i++) { nums[i] = i + 1; } - // システム時刻を使用してランダムシードを生成 + // システム時刻を使って乱数シードを生成する unsigned seed = chrono::system_clock::now().time_since_epoch().count(); // 配列要素をランダムにシャッフル shuffle(nums.begin(), nums.end(), default_random_engine(seed)); return nums; } -/* 配列 nums で数値1のインデックスを見つける */ +/* 配列 nums 内で数値 1 のインデックスを探す */ int findOne(vector &nums) { for (int i = 0; i < nums.size(); i++) { - // 要素1が配列の先頭にある場合、最良時間計算量 O(1) を達成 - // 要素1が配列の末尾にある場合、最悪時間計算量 O(n) を達成 + // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる + // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる if (nums[i] == 1) return i; } return -1; } -/* ドライバーコード */ +/* Driver Code */ int main() { for (int i = 0; i < 1000; i++) { int n = 100; vector nums = randomNumbers(n); int index = findOne(nums); - cout << "\n配列 [ 1, 2, ..., n ] をシャッフル後 = "; + cout << "\n配列 [ 1, 2, ..., n ] をシャッフルした後 = "; printVector(nums); - cout << "数値1のインデックスは " << index << endl; + cout << "数字 1 のインデックスは " << index << endl; } return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt b/ja/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt new file mode 100644 index 000000000..38dfff710 --- /dev/null +++ b/ja/codes/cpp/chapter_divide_and_conquer/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(binary_search_recur binary_search_recur.cpp) +add_executable(build_tree build_tree.cpp) +add_executable(hanota hanota.cpp) \ No newline at end of file diff --git a/ja/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp b/ja/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp index ca1c0608c..63a172ed8 100644 --- a/ja/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp +++ b/ja/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp @@ -8,20 +8,20 @@ /* 二分探索:問題 f(i, j) */ int dfs(vector &nums, int target, int i, int j) { - // 区間が空の場合、対象要素が存在しないことを示すため、-1 を返す + // 区間が空なら対象要素は存在しないので -1 を返す if (i > j) { return -1; } // 中点インデックス m を計算 - int m = i + (j - i) / 2; + int m = (i + j) / 2; if (nums[m] < target) { - // 再帰的な部分問題 f(m+1, j) + // 部分問題 f(m+1, j) を再帰的に解く return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { - // 再帰的な部分問題 f(i, m-1) + // 部分問題 f(i, m-1) を再帰的に解く return dfs(nums, target, i, m - 1); } else { - // 対象要素が見つかったため、そのインデックスを返す + // 目標要素が見つかったらそのインデックスを返す return m; } } @@ -33,14 +33,14 @@ int binarySearch(vector &nums, int target) { return dfs(nums, target, 0, n - 1); } -/* ドライバーコード */ +/* Driver Code */ int main() { int target = 6; vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; - // 二分探索(両端閉区間) + // 二分探索(両閉区間) int index = binarySearch(nums, target); - cout << "対象要素 6 のインデックス =" << index << endl; + cout << "対象要素 6 のインデックス = " << index << endl; return 0; } \ No newline at end of file diff --git a/ja/codes/cpp/chapter_divide_and_conquer/build_tree.cpp b/ja/codes/cpp/chapter_divide_and_conquer/build_tree.cpp index 23d3ce215..52912dcfd 100644 --- a/ja/codes/cpp/chapter_divide_and_conquer/build_tree.cpp +++ b/ja/codes/cpp/chapter_divide_and_conquer/build_tree.cpp @@ -6,26 +6,26 @@ #include "../utils/common.hpp" -/* 二分木の構築:分割統治 */ +/* 二分木を構築:分割統治 */ TreeNode *dfs(vector &preorder, unordered_map &inorderMap, int i, int l, int r) { - // 部分木の区間が空の場合に終了 + // 部分木区間が空なら終了する if (r - l < 0) return NULL; - // ルートノードを初期化 + // ルートノードを初期化する TreeNode *root = new TreeNode(preorder[i]); - // m を問い合わせて左右の部分木を分割 + // m を求めて左右部分木を分割する int m = inorderMap[preorder[i]]; - // 部分問題:左の部分木を構築 + // 部分問題:左部分木を構築する root->left = dfs(preorder, inorderMap, i + 1, l, m - 1); - // 部分問題:右の部分木を構築 + // 部分問題:右部分木を構築する root->right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); - // ルートノードを返す + // 根ノードを返す return root; } -/* 二分木の構築 */ +/* 二分木を構築 */ TreeNode *buildTree(vector &preorder, vector &inorder) { - // ハッシュテーブルを初期化し、中間順序の要素からインデックスへのマッピングを格納 + // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する unordered_map inorderMap; for (int i = 0; i < inorder.size(); i++) { inorderMap[inorder[i]] = i; @@ -34,18 +34,18 @@ TreeNode *buildTree(vector &preorder, vector &inorder) { return root; } -/* ドライバーコード */ +/* Driver Code */ int main() { vector preorder = {3, 9, 2, 1, 7}; vector inorder = {9, 3, 1, 2, 7}; cout << "前順走査 = "; printVector(preorder); - cout << "中間順序走査 = "; + cout << "中順走査 = "; printVector(inorder); TreeNode *root = buildTree(preorder, inorder); - cout << "構築された二分木:\n"; + cout << "構築した二分木:\n"; printTree(root); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_divide_and_conquer/hanota.cpp b/ja/codes/cpp/chapter_divide_and_conquer/hanota.cpp index 918d7be3c..9682e2b0b 100644 --- a/ja/codes/cpp/chapter_divide_and_conquer/hanota.cpp +++ b/ja/codes/cpp/chapter_divide_and_conquer/hanota.cpp @@ -6,45 +6,45 @@ #include "../utils/common.hpp" -/* 円盤を移動 */ +/* 円盤を 1 枚移動 */ void move(vector &src, vector &tar) { - // src の最上部から円盤を取り出す + // src の上から円盤を1枚取り出す int pan = src.back(); src.pop_back(); - // 円盤を tar の最上部に配置 + // 円盤を tar の上に置く tar.push_back(pan); } -/* ハノイの塔問題 f(i) を解く */ +/* ハノイの塔の問題 f(i) を解く */ void dfs(int i, vector &src, vector &buf, vector &tar) { - // src に円盤が1つだけ残っている場合、それを tar に移動 + // src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す if (i == 1) { move(src, tar); return; } - // 部分問題 f(i-1):tar の助けを借りて、上位 i-1 個の円盤を src から buf に移動 + // 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す dfs(i - 1, src, tar, buf); - // 部分問題 f(1):残りの1つの円盤を src から tar に移動 + // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す move(src, tar); - // 部分問題 f(i-1):src の助けを借りて、上位 i-1 個の円盤を buf から tar に移動 + // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す dfs(i - 1, buf, src, tar); } -/* ハノイの塔問題を解く */ +/* ハノイの塔を解く */ void solveHanota(vector &A, vector &B, vector &C) { int n = A.size(); - // B の助けを借りて、上位 n 個の円盤を A から C に移動 + // A の上から n 枚の円盤を B を介して C へ移す dfs(n, A, B, C); } -/* ドライバーコード */ +/* Driver Code */ int main() { - // リストの末尾が柱の最上部 + // リスト末尾が柱の頂上 vector A = {5, 4, 3, 2, 1}; vector B = {}; vector C = {}; - cout << "初期状態:\n"; + cout << "初期状態:\n"; cout << "A ="; printVector(A); cout << "B ="; @@ -54,7 +54,7 @@ int main() { solveHanota(A, B, C); - cout << "円盤移動後:\n"; + cout << "円盤の移動完了後:\n"; cout << "A ="; printVector(A); cout << "B ="; @@ -63,4 +63,4 @@ int main() { printVector(C); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_dynamic_programming/CMakeLists.txt b/ja/codes/cpp/chapter_dynamic_programming/CMakeLists.txt new file mode 100644 index 000000000..ed185458a --- /dev/null +++ b/ja/codes/cpp/chapter_dynamic_programming/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(climbing_stairs_backtrack climbing_stairs_backtrack.cpp) +add_executable(climbing_stairs_dfs climbing_stairs_dfs.cpp) +add_executable(climbing_stairs_dfs_mem climbing_stairs_dfs_mem.cpp) +add_executable(climbing_stairs_dp climbing_stairs_dp.cpp) +add_executable(min_cost_climbing_stairs_dp min_cost_climbing_stairs_dp.cpp) +add_executable(min_path_sum min_path_sum.cpp) +add_executable(unbounded_knapsack unbounded_knapsack.cpp) +add_executable(coin_change coin_change.cpp) +add_executable(coin_change_ii coin_change_ii.cpp) +add_executable(edit_distance edit_distance.cpp) \ No newline at end of file diff --git a/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp b/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp index 029aff547..c9c62df6d 100644 --- a/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp +++ b/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp @@ -1,3 +1,4 @@ + /** * File: climbing_stairs_backtrack.cpp * Created Time: 2023-06-30 @@ -8,35 +9,35 @@ /* バックトラッキング */ void backtrack(vector &choices, int state, int n, vector &res) { - // n段目に到達したとき、解の数に1を加える + // 第 n 段に到達したら、方法数を 1 増やす if (state == n) res[0]++; // すべての選択肢を走査 for (auto &choice : choices) { - // 剪定:n段を超えて登ることを許可しない + // 枝刈り: 第 n 段を超えないようにする if (state + choice > n) continue; - // 試行:選択を行い、状態を更新 + // 試行: 選択を行い、状態を更新 backtrack(choices, state + choice, n, res); - // 撤回 + // バックトラック } } /* 階段登り:バックトラッキング */ int climbingStairsBacktrack(int n) { - vector choices = {1, 2}; // 1段または2段登ることを選択可能 - int state = 0; // 0段目から登り始める - vector res = {0}; // res[0] を使用して解の数を記録 + vector choices = {1, 2}; // 1 段または 2 段上ることを選べる + int state = 0; // 第 0 段から上り始める + vector res = {0}; // res[0] を使って方法数を記録する backtrack(choices, state, n, res); return res[0]; } -/* ドライバーコード */ +/* Driver Code */ int main() { int n = 9; int res = climbingStairsBacktrack(n); - cout << n << "段の階段を登る解は" << res << "通りです" << endl; + cout << "階段を " << n << " 段上る方法は全部で " << res << " 通り" << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp b/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp index c8d317aec..1c8491c8c 100644 --- a/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp +++ b/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp @@ -6,19 +6,19 @@ #include "../utils/common.hpp" -/* 制約付き階段登り:動的プログラミング */ +/* 制約付き階段登り:動的計画法 */ int climbingStairsConstraintDP(int n) { if (n == 1 || n == 2) { return 1; } - // DPテーブルを初期化し、部分問題の解を格納するために使用 + // 部分問題の解を保存するために dp テーブルを初期化 vector> dp(n + 1, vector(3, 0)); - // 初期状態:最小の部分問題の解を事前設定 + // 初期状態:最小部分問題の解をあらかじめ設定 dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; - // 状態遷移:小さな問題から大きな部分問題を段階的に解く + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (int i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; @@ -26,12 +26,12 @@ int climbingStairsConstraintDP(int n) { return dp[n][1] + dp[n][2]; } -/* ドライバーコード */ +/* Driver Code */ int main() { int n = 9; int res = climbingStairsConstraintDP(n); - cout << n << "段の階段を登る解は" << res << "通りです" << endl; + cout << "階段を " << n << " 段上る方法は全部で " << res << " 通り" << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp b/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp index 37cdbbd56..e782ab711 100644 --- a/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp +++ b/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp @@ -6,9 +6,9 @@ #include "../utils/common.hpp" -/* 探索 */ +/* 検索 */ int dfs(int i) { - // 既知の dp[1] と dp[2] を返す + // dp[1] と dp[2] は既知なので返す if (i == 1 || i == 2) return i; // dp[i] = dp[i-1] + dp[i-2] @@ -21,12 +21,12 @@ int climbingStairsDFS(int n) { return dfs(n); } -/* ドライバーコード */ +/* Driver Code */ int main() { int n = 9; int res = climbingStairsDFS(n); - cout << n << "段の階段を登る解は" << res << "通りです" << endl; + cout << "階段を " << n << " 段上る方法は全部で " << res << " 通り" << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp b/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp index df8191eac..d1518e2b7 100644 --- a/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp +++ b/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp @@ -8,32 +8,32 @@ /* メモ化探索 */ int dfs(int i, vector &mem) { - // 既知の dp[1] と dp[2] を返す + // dp[1] と dp[2] は既知なので返す if (i == 1 || i == 2) return i; - // dp[i] の記録がある場合、それを返す + // dp[i] の記録があれば、それをそのまま返す if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] int count = dfs(i - 1, mem) + dfs(i - 2, mem); - // dp[i] を記録 + // dp[i] を記録する mem[i] = count; return count; } /* 階段登り:メモ化探索 */ int climbingStairsDFSMem(int n) { - // mem[i] は i 段目に登る総解数を記録、-1 は記録なしを意味する + // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す vector mem(n + 1, -1); return dfs(n, mem); } -/* ドライバーコード */ +/* Driver Code */ int main() { int n = 9; int res = climbingStairsDFSMem(n); - cout << n << "段の階段を登る解は" << res << "通りです" << endl; + cout << "階段を " << n << " 段上る方法は全部で " << res << " 通り" << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp b/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp index 5e96f7416..645024569 100644 --- a/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp +++ b/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp @@ -6,23 +6,23 @@ #include "../utils/common.hpp" -/* 階段登り:動的プログラミング */ +/* 階段登り:動的計画法 */ int climbingStairsDP(int n) { if (n == 1 || n == 2) return n; - // DPテーブルを初期化し、部分問題の解を格納するために使用 + // 部分問題の解を保存するために dp テーブルを初期化 vector dp(n + 1); - // 初期状態:最小の部分問題の解を事前設定 + // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = 1; dp[2] = 2; - // 状態遷移:小さな問題から大きな部分問題を段階的に解く + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } -/* 階段登り:空間最適化動的プログラミング */ +/* 階段登り:空間最適化した動的計画法 */ int climbingStairsDPComp(int n) { if (n == 1 || n == 2) return n; @@ -35,15 +35,15 @@ int climbingStairsDPComp(int n) { return b; } -/* ドライバーコード */ +/* Driver Code */ int main() { int n = 9; int res = climbingStairsDP(n); - cout << n << "段の階段を登る解は" << res << "通りです" << endl; + cout << "階段を " << n << " 段上る方法は全部で " << res << " 通り" << endl; res = climbingStairsDPComp(n); - cout << n << "段の階段を登る解は" << res << "通りです" << endl; + cout << "階段を " << n << " 段上る方法は全部で " << res << " 通り" << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_dynamic_programming/coin_change.cpp b/ja/codes/cpp/chapter_dynamic_programming/coin_change.cpp index e67f9471f..ed7aa0d02 100644 --- a/ja/codes/cpp/chapter_dynamic_programming/coin_change.cpp +++ b/ja/codes/cpp/chapter_dynamic_programming/coin_change.cpp @@ -6,24 +6,24 @@ #include "../utils/common.hpp" -/* 硬貨両替:動的プログラミング */ +/* コイン両替:動的計画法 */ int coinChangeDP(vector &coins, int amt) { int n = coins.size(); int MAX = amt + 1; - // DPテーブルを初期化 + // dp テーブルを初期化 vector> dp(n + 1, vector(amt + 1, 0)); - // 状態遷移:最初の行と最初の列 + // 状態遷移:先頭行と先頭列 for (int a = 1; a <= amt; a++) { dp[0][a] = MAX; } - // 状態遷移:残りの行と列 + // 状態遷移: 残りの行と列 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { - // 目標金額を超える場合、硬貨 i を選択しない + // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a]; } else { - // 選択しない場合と硬貨 i を選択する場合のより小さい値 + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } @@ -31,21 +31,21 @@ int coinChangeDP(vector &coins, int amt) { return dp[n][amt] != MAX ? dp[n][amt] : -1; } -/* 硬貨両替:空間最適化動的プログラミング */ +/* コイン交換:空間最適化後の動的計画法 */ int coinChangeDPComp(vector &coins, int amt) { int n = coins.size(); int MAX = amt + 1; - // DPテーブルを初期化 + // dp テーブルを初期化 vector dp(amt + 1, MAX); dp[0] = 0; // 状態遷移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { - // 目標金額を超える場合、硬貨 i を選択しない + // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a]; } else { - // 選択しない場合と硬貨 i を選択する場合のより小さい値 + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1); } } @@ -53,18 +53,18 @@ int coinChangeDPComp(vector &coins, int amt) { return dp[amt] != MAX ? dp[amt] : -1; } -/* ドライバーコード */ +/* Driver code */ int main() { vector coins = {1, 2, 5}; int amt = 4; - // 動的プログラミング + // 動的計画法 int res = coinChangeDP(coins, amt); - cout << "目標金額を作るのに必要な最小硬貨数は " << res << " です" << endl; + cout << "目標金額を作るのに必要な最小硬貨枚数は " << res << endl; - // 空間最適化動的プログラミング + // 空間最適化後の動的計画法 res = coinChangeDPComp(coins, amt); - cout << "目標金額を作るのに必要な最小硬貨数は " << res << " です" << endl; + cout << "目標金額を作るのに必要な最小硬貨枚数は " << res << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp b/ja/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp index 4b3bb93dc..c7f03d614 100644 --- a/ja/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp +++ b/ja/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp @@ -6,12 +6,12 @@ #include "../utils/common.hpp" -/* 硬貨両替 II:動的プログラミング */ +/* コイン両替 II:動的計画法 */ int coinChangeIIDP(vector &coins, int amt) { int n = coins.size(); - // DPテーブルを初期化 + // dp テーブルを初期化 vector> dp(n + 1, vector(amt + 1, 0)); - // 最初の列を初期化 + // 先頭列を初期化する for (int i = 0; i <= n; i++) { dp[i][0] = 1; } @@ -19,10 +19,10 @@ int coinChangeIIDP(vector &coins, int amt) { for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { - // 目標金額を超える場合、硬貨 i を選択しない + // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a]; } else { - // 選択しない場合と硬貨 i を選択する場合の2つの選択肢の合計 + // コイン i を選ばない場合と選ぶ場合の和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } @@ -30,20 +30,20 @@ int coinChangeIIDP(vector &coins, int amt) { return dp[n][amt]; } -/* 硬貨両替 II:空間最適化動的プログラミング */ +/* コイン両替 II:空間最適化した動的計画法 */ int coinChangeIIDPComp(vector &coins, int amt) { int n = coins.size(); - // DPテーブルを初期化 + // dp テーブルを初期化 vector dp(amt + 1, 0); dp[0] = 1; // 状態遷移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { - // 目標金額を超える場合、硬貨 i を選択しない + // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a]; } else { - // 選択しない場合と硬貨 i を選択する場合の2つの選択肢の合計 + // コイン i を選ばない場合と選ぶ場合の和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } @@ -51,18 +51,18 @@ int coinChangeIIDPComp(vector &coins, int amt) { return dp[amt]; } -/* ドライバーコード */ +/* Driver code */ int main() { vector coins = {1, 2, 5}; int amt = 5; - // 動的プログラミング + // 動的計画法 int res = coinChangeIIDP(coins, amt); - cout << "目標金額を作る硬貨の組み合わせ数は " << res << " です" << endl; + cout << "目標金額を作る硬貨の組み合わせ数は " << res << endl; - // 空間最適化動的プログラミング + // 空間最適化後の動的計画法 res = coinChangeIIDPComp(coins, amt); - cout << "目標金額を作る硬貨の組み合わせ数は " << res << " です" << endl; + cout << "目標金額を作る硬貨の組み合わせ数は " << res << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_dynamic_programming/edit_distance.cpp b/ja/codes/cpp/chapter_dynamic_programming/edit_distance.cpp index 3abce88b2..e887d6007 100644 --- a/ja/codes/cpp/chapter_dynamic_programming/edit_distance.cpp +++ b/ja/codes/cpp/chapter_dynamic_programming/edit_distance.cpp @@ -6,47 +6,73 @@ #include "../utils/common.hpp" -/* 編集距離:ブルートフォース探索 */ +/* 編集距離:総当たり探索 */ int editDistanceDFS(string s, string t, int i, int j) { - // s と t の両方が空の場合、0 を返す + // s と t がともに空なら 0 を返す if (i == 0 && j == 0) return 0; - // s が空の場合、t の長さを返す + // s が空なら t の長さを返す if (i == 0) return j; - // t が空の場合、s の長さを返す + // t が空なら s の長さを返す if (j == 0) return i; - // 2つの文字が等しい場合、これら2つの文字をスキップ + // 2 つの文字が等しければ、その 2 文字をそのままスキップする if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1); - // 最小編集数 = 3つの操作(挿入、削除、置換)からの最小編集数 + 1 + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 int insert = editDistanceDFS(s, t, i, j - 1); int del = editDistanceDFS(s, t, i - 1, j); int replace = editDistanceDFS(s, t, i - 1, j - 1); - // 最小編集数を返す + // 最小編集回数を返す return min(min(insert, del), replace) + 1; } -/* 編集距離:動的プログラミング */ +/* 編集距離:メモ化探索 */ +int editDistanceDFSMem(string s, string t, vector> &mem, int i, int j) { + // s と t がともに空なら 0 を返す + if (i == 0 && j == 0) + return 0; + // s が空なら t の長さを返す + if (i == 0) + return j; + // t が空なら s の長さを返す + if (j == 0) + return i; + // 記録済みなら、それをそのまま返す + if (mem[i][j] != -1) + return mem[i][j]; + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if (s[i - 1] == t[j - 1]) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + int insert = editDistanceDFSMem(s, t, mem, i, j - 1); + int del = editDistanceDFSMem(s, t, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 最小編集回数を記録して返す + mem[i][j] = min(min(insert, del), replace) + 1; + return mem[i][j]; +} + +/* 編集距離:動的計画法 */ int editDistanceDP(string s, string t) { int n = s.length(), m = t.length(); vector> dp(n + 1, vector(m + 1, 0)); - // 状態遷移:最初の行と最初の列 + // 状態遷移:先頭行と先頭列 for (int i = 1; i <= n; i++) { dp[i][0] = i; } for (int j = 1; j <= m; j++) { dp[0][j] = j; } - // 状態遷移:残りの行と列 + // 状態遷移: 残りの行と列 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s[i - 1] == t[j - 1]) { - // 2つの文字が等しい場合、これら2つの文字をスキップ + // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[i][j] = dp[i - 1][j - 1]; } else { - // 最小編集数 = 3つの操作(挿入、削除、置換)からの最小編集数 + 1 + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } @@ -54,19 +80,57 @@ int editDistanceDP(string s, string t) { return dp[n][m]; } -/* ドライバーコード */ +/* 編集距離:空間最適化した動的計画法 */ +int editDistanceDPComp(string s, string t) { + int n = s.length(), m = t.length(); + vector dp(m + 1, 0); + // 状態遷移:先頭行 + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // 状態遷移:残りの行 + for (int i = 1; i <= n; i++) { + // 状態遷移:先頭列 + int leftup = dp[0]; // dp[i-1, j-1] を一時保存する + dp[0] = i; + // 状態遷移:残りの列 + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[j] = leftup; + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 次の反復の dp[i-1, j-1] に更新する + } + } + return dp[m]; +} + +/* Driver Code */ int main() { string s = "bag"; string t = "pack"; int n = s.length(), m = t.length(); - // ブルートフォース探索 + // 全探索 int res = editDistanceDFS(s, t, n, m); - cout << s << " を " << t << " に変更するには最低 " << res << " 回の編集が必要です" << endl; + cout << s << " を " << t << " に変更するには最小で " << res << " 回の編集が必要\n"; - // 動的プログラミング + // メモ化探索 + vector> mem(n + 1, vector(m + 1, -1)); + res = editDistanceDFSMem(s, t, mem, n, m); + cout << s << " を " << t << " に変更するには最小で " << res << " 回の編集が必要\n"; + + // 動的計画法 res = editDistanceDP(s, t); - cout << s << " を " << t << " に変更するには最低 " << res << " 回の編集が必要です" << endl; + cout << s << " を " << t << " に変更するには最小で " << res << " 回の編集が必要\n"; + + // 空間最適化後の動的計画法 + res = editDistanceDPComp(s, t); + cout << s << " を " << t << " に変更するには最小で " << res << " 回の編集が必要\n"; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_dynamic_programming/knapsack.cpp b/ja/codes/cpp/chapter_dynamic_programming/knapsack.cpp index a928d55ac..ca92852fc 100644 --- a/ja/codes/cpp/chapter_dynamic_programming/knapsack.cpp +++ b/ja/codes/cpp/chapter_dynamic_programming/knapsack.cpp @@ -1,41 +1,61 @@ -/** - * File: knapsack.cpp - * Created Time: 2023-07-10 - * Author: krahets (krahets@163.com) - */ +#include +#include +#include -#include "../utils/common.hpp" +using namespace std; -/* 0-1 ナップサック:ブルートフォース探索 */ +/* 0-1 ナップサック:総当たり探索 */ int knapsackDFS(vector &wgt, vector &val, int i, int c) { - // すべてのアイテムが選択されたか、ナップサックに残り容量がない場合、値 0 を返す + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if (i == 0 || c == 0) { return 0; } - // ナップサックの容量を超える場合、ナップサックに入れないことしか選択できない + // ナップサック容量を超える場合は、入れない選択しかできない if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } - // アイテム i を入れない場合と入れる場合の最大値を計算 + // 品物 i を入れない場合と入れる場合の最大価値を計算する int no = knapsackDFS(wgt, val, i - 1, c); int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; - // 2つの選択肢のより大きい値を返す + // 2つの案のうち価値が大きいほうを返す return max(no, yes); } -/* 0-1 ナップサック:動的プログラミング */ +/* 0-1 ナップサック:メモ化探索 */ +int knapsackDFSMem(vector &wgt, vector &val, vector> &mem, int i, int c) { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if (i == 0 || c == 0) { + return 0; + } + // 既に記録があればそのまま返す + if (mem[i][c] != -1) { + return mem[i][c]; + } + // ナップサック容量を超える場合は、入れない選択しかできない + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + int no = knapsackDFSMem(wgt, val, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 2 つの案のうち価値が大きい方を記録して返す + mem[i][c] = max(no, yes); + return mem[i][c]; +} + +/* 0-1 ナップサック:動的計画法 */ int knapsackDP(vector &wgt, vector &val, int cap) { int n = wgt.size(); - // DPテーブルを初期化 + // dp テーブルを初期化 vector> dp(n + 1, vector(cap + 1, 0)); // 状態遷移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { - // ナップサックの容量を超える場合、アイテム i を選択しない + // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c]; } else { - // 選択しない場合とアイテム i を選択する場合のより大きい値 + // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); } } @@ -43,17 +63,17 @@ int knapsackDP(vector &wgt, vector &val, int cap) { return dp[n][cap]; } -/* 0-1 ナップサック:空間最適化動的プログラミング */ +/* 0-1 ナップサック:空間最適化後の動的計画法 */ int knapsackDPComp(vector &wgt, vector &val, int cap) { int n = wgt.size(); - // DPテーブルを初期化 + // dp テーブルを初期化 vector dp(cap + 1, 0); // 状態遷移 for (int i = 1; i <= n; i++) { - // 逆順で走査 + // 逆順に走査する for (int c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { - // 選択しない場合とアイテム i を選択する場合のより大きい値 + // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } @@ -61,24 +81,29 @@ int knapsackDPComp(vector &wgt, vector &val, int cap) { return dp[cap]; } -/* ドライバーコード */ +/* Driver Code */ int main() { vector wgt = {10, 20, 30, 40, 50}; vector val = {50, 120, 150, 210, 240}; int cap = 50; int n = wgt.size(); - // ブルートフォース探索 + // 全探索 int res = knapsackDFS(wgt, val, n, cap); - cout << "ナップサック容量内での最大値は " << res << " です" << endl; + cout << "ナップサック容量を超えない最大価値は " << res << endl; - // 動的プログラミング + // メモ化探索 + vector> mem(n + 1, vector(cap + 1, -1)); + res = knapsackDFSMem(wgt, val, mem, n, cap); + cout << "ナップサック容量を超えない最大価値は " << res << endl; + + // 動的計画法 res = knapsackDP(wgt, val, cap); - cout << "ナップサック容量内での最大値は " << res << " です" << endl; + cout << "ナップサック容量を超えない最大価値は " << res << endl; - // 空間最適化動的プログラミング + // 空間最適化後の動的計画法 res = knapsackDPComp(wgt, val, cap); - cout << "ナップサック容量内での最大値は " << res << " です" << endl; + cout << "ナップサック容量を超えない最大価値は " << res << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp b/ja/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp index eb0b6713e..cf3323b2b 100644 --- a/ja/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp +++ b/ja/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp @@ -6,24 +6,24 @@ #include "../utils/common.hpp" -/* 最小コスト階段登り:動的プログラミング */ +/* 階段登りの最小コスト:動的計画法 */ int minCostClimbingStairsDP(vector &cost) { int n = cost.size() - 1; if (n == 1 || n == 2) return cost[n]; - // DPテーブルを初期化し、部分問題の解を格納するために使用 + // 部分問題の解を保存するために dp テーブルを初期化 vector dp(n + 1); - // 初期状態:最小の部分問題の解を事前設定 + // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = cost[1]; dp[2] = cost[2]; - // 状態遷移:小さな問題から大きな部分問題を段階的に解く + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (int i = 3; i <= n; i++) { dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } -/* 最小コスト階段登り:空間最適化動的プログラミング */ +/* 階段昇りの最小コスト:空間最適化後の動的計画法 */ int minCostClimbingStairsDPComp(vector &cost) { int n = cost.size() - 1; if (n == 1 || n == 2) @@ -37,21 +37,17 @@ int minCostClimbingStairsDPComp(vector &cost) { return b; } -/* ドライバーコード */ +/* Driver Code */ int main() { vector cost = {0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1}; - cout << "階段のコストリストを ["; - for (int i = 0; i < cost.size(); i++) { - cout << cost[i]; - if (i < cost.size() - 1) cout << ", "; - } - cout << "] として入力" << endl; + cout << "入力された階段コストのリストは "; + printVector(cost); int res = minCostClimbingStairsDP(cost); - cout << "階段を登るための最小コスト " << res << endl; + cout << "階段を上り切る最小コストは " << res << endl; res = minCostClimbingStairsDPComp(cost); - cout << "階段を登るための最小コスト " << res << endl; + cout << "階段を上り切る最小コストは " << res << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp b/ja/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp index 0f92ba1f6..d9b274d26 100644 --- a/ja/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp +++ b/ja/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp @@ -6,38 +6,60 @@ #include "../utils/common.hpp" -/* 最小パス和:ブルートフォース探索 */ +/* 最小経路和:全探索 */ int minPathSumDFS(vector> &grid, int i, int j) { - // 左上のセルの場合、探索を終了 + // 左上のセルなら探索を終了する if (i == 0 && j == 0) { return grid[0][0]; } - // 行または列のインデックスが範囲外の場合、+∞ のコストを返す + // 行または列のインデックスが範囲外なら、コスト +∞ を返す if (i < 0 || j < 0) { return INT_MAX; } - // 左上から (i-1, j) と (i, j-1) への最小パスコストを計算 + // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する int up = minPathSumDFS(grid, i - 1, j); int left = minPathSumDFS(grid, i, j - 1); - // 左上から (i, j) への最小パスコストを返す - return min(left, up) + grid[i][j]; + // 左上隅から (i, j) までの最小経路コストを返す + return min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; } -/* 最小パス和:動的プログラミング */ +/* 最小経路和:メモ化探索 */ +int minPathSumDFSMem(vector> &grid, vector> &mem, int i, int j) { + // 左上のセルなら探索を終了する + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if (i < 0 || j < 0) { + return INT_MAX; + } + // 既に記録があればそのまま返す + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左と上のセルからの最小経路コスト + int up = minPathSumDFSMem(grid, mem, i - 1, j); + int left = minPathSumDFSMem(grid, mem, i, j - 1); + // 左上から (i, j) までの最小経路コストを記録して返す + mem[i][j] = min(left, up) != INT_MAX ? min(left, up) + grid[i][j] : INT_MAX; + return mem[i][j]; +} + +/* 最小経路和:動的計画法 */ int minPathSumDP(vector> &grid) { int n = grid.size(), m = grid[0].size(); - // DPテーブルを初期化 + // dp テーブルを初期化 vector> dp(n, vector(m)); dp[0][0] = grid[0][0]; - // 状態遷移:最初の行 + // 状態遷移:先頭行 for (int j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } - // 状態遷移:最初の列 + // 状態遷移:先頭列 for (int i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } - // 状態遷移:残りの行と列 + // 状態遷移: 残りの行と列 for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; @@ -46,23 +68,49 @@ int minPathSumDP(vector> &grid) { return dp[n - 1][m - 1]; } -/* ドライバーコード */ +/* 最小経路和:空間最適化後の動的計画法 */ +int minPathSumDPComp(vector> &grid) { + int n = grid.size(), m = grid[0].size(); + // dp テーブルを初期化 + vector dp(m); + // 状態遷移:先頭行 + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状態遷移:残りの行 + for (int i = 1; i < n; i++) { + // 状態遷移:先頭列 + dp[0] = dp[0] + grid[i][0]; + // 状態遷移:残りの列 + for (int j = 1; j < m; j++) { + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ int main() { - vector> grid = { - {1, 3, 1, 5}, - {2, 2, 4, 2}, - {5, 3, 2, 1}, - {4, 3, 5, 2} - }; + vector> grid = {{1, 3, 1, 5}, {2, 2, 4, 2}, {5, 3, 2, 1}, {4, 3, 5, 2}}; int n = grid.size(), m = grid[0].size(); - // ブルートフォース探索 + // 全探索 int res = minPathSumDFS(grid, n - 1, m - 1); - cout << "左上角から右下角への最小パス和は " << res << " です" << endl; + cout << "左上から右下までの最小経路和は " << res << endl; - // 動的プログラミング + // メモ化探索 + vector> mem(n, vector(m, -1)); + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + cout << "左上から右下までの最小経路和は " << res << endl; + + // 動的計画法 res = minPathSumDP(grid); - cout << "左上角から右下角への最小パス和は " << res << " です" << endl; + cout << "左上から右下までの最小経路和は " << res << endl; + + // 空間最適化後の動的計画法 + res = minPathSumDPComp(grid); + cout << "左上から右下までの最小経路和は " << res << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp b/ja/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp index 605562833..c4987ed10 100644 --- a/ja/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp +++ b/ja/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp @@ -6,19 +6,19 @@ #include "../utils/common.hpp" -/* 完全ナップサック:動的プログラミング */ +/* 完全ナップサック問題:動的計画法 */ int unboundedKnapsackDP(vector &wgt, vector &val, int cap) { int n = wgt.size(); - // DPテーブルを初期化 + // dp テーブルを初期化 vector> dp(n + 1, vector(cap + 1, 0)); // 状態遷移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { - // ナップサックの容量を超える場合、アイテム i を選択しない + // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c]; } else { - // 選択しない場合とアイテム i を選択する場合のより大きい値 + // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); } } @@ -26,19 +26,19 @@ int unboundedKnapsackDP(vector &wgt, vector &val, int cap) { return dp[n][cap]; } -/* 完全ナップサック:空間最適化動的プログラミング */ +/* 完全ナップサック問題:空間最適化後の動的計画法 */ int unboundedKnapsackDPComp(vector &wgt, vector &val, int cap) { int n = wgt.size(); - // DPテーブルを初期化 + // dp テーブルを初期化 vector dp(cap + 1, 0); // 状態遷移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { - // ナップサックの容量を超える場合、アイテム i を選択しない + // ナップサック容量を超えるなら品物 i は選ばない dp[c] = dp[c]; } else { - // 選択しない場合とアイテム i を選択する場合のより大きい値 + // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } @@ -46,19 +46,19 @@ int unboundedKnapsackDPComp(vector &wgt, vector &val, int cap) { return dp[cap]; } -/* ドライバーコード */ +/* Driver code */ int main() { vector wgt = {1, 2, 3}; vector val = {5, 11, 15}; int cap = 4; - // 動的プログラミング + // 動的計画法 int res = unboundedKnapsackDP(wgt, val, cap); - cout << "ナップサック容量内での最大値は " << res << " です" << endl; + cout << "ナップサック容量を超えない最大価値は " << res << endl; - // 空間最適化動的プログラミング + // 空間最適化後の動的計画法 res = unboundedKnapsackDPComp(wgt, val, cap); - cout << "ナップサック容量内での最大値は " << res << " です" << endl; + cout << "ナップサック容量を超えない最大価値は " << res << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_graph/CMakeLists.txt b/ja/codes/cpp/chapter_graph/CMakeLists.txt new file mode 100644 index 000000000..4a56ce35b --- /dev/null +++ b/ja/codes/cpp/chapter_graph/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(graph_bfs graph_bfs.cpp) +add_executable(graph_dfs graph_dfs.cpp) +# add_executable(graph_adjacency_list graph_adjacency_list.cpp) +add_executable(graph_adjacency_list_test graph_adjacency_list_test.cpp) +add_executable(graph_adjacency_matrix graph_adjacency_matrix.cpp) diff --git a/ja/codes/cpp/chapter_graph/graph_adjacency_list.cpp b/ja/codes/cpp/chapter_graph/graph_adjacency_list.cpp index b661ac421..4c36350d9 100644 --- a/ja/codes/cpp/chapter_graph/graph_adjacency_list.cpp +++ b/ja/codes/cpp/chapter_graph/graph_adjacency_list.cpp @@ -9,10 +9,10 @@ /* 隣接リストに基づく無向グラフクラス */ class GraphAdjList { public: - // 隣接リスト、キー:頂点、値:その頂点のすべての隣接頂点 + // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 unordered_map> adjList; - /* ベクターから指定されたノードを削除 */ + /* vector 内の指定ノードを削除 */ void remove(vector &vec, Vertex *vet) { for (int i = 0; i < vec.size(); i++) { if (vec[i] == vet) { @@ -40,7 +40,7 @@ class GraphAdjList { /* 辺を追加 */ void addEdge(Vertex *vet1, Vertex *vet2) { if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) - throw invalid_argument("Vertex does not exist"); + throw invalid_argument("頂点が存在しません"); // 辺 vet1 - vet2 を追加 adjList[vet1].push_back(vet2); adjList[vet2].push_back(vet1); @@ -49,7 +49,7 @@ class GraphAdjList { /* 辺を削除 */ void removeEdge(Vertex *vet1, Vertex *vet2) { if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) - throw invalid_argument("Vertex does not exist"); + throw invalid_argument("頂点が存在しません"); // 辺 vet1 - vet2 を削除 remove(adjList[vet1], vet2); remove(adjList[vet2], vet1); @@ -59,23 +59,23 @@ class GraphAdjList { void addVertex(Vertex *vet) { if (adjList.count(vet)) return; - // 隣接リストに新しい連結リストを追加 + // 隣接リストに新しいリストを追加 adjList[vet] = vector(); } /* 頂点を削除 */ void removeVertex(Vertex *vet) { if (!adjList.count(vet)) - throw invalid_argument("Vertex does not exist"); - // 隣接リストから頂点vetに対応する連結リストを削除 + throw invalid_argument("頂点が存在しません"); + // 隣接リストから頂点 vet に対応するリストを削除 adjList.erase(vet); - // 他の頂点の連結リストを走査し、vetを含むすべての辺を削除 + // 他の頂点のリストを走査し、vet を含むすべての辺を削除 for (auto &adj : adjList) { remove(adj.second, vet); } } - /* 隣接リストを印刷 */ + /* 隣接リストを出力 */ void print() { cout << "隣接リスト =" << endl; for (auto &adj : adjList) { @@ -87,4 +87,4 @@ class GraphAdjList { } }; -// テストケースはgraph_adjacency_list_test.cppを参照 \ No newline at end of file +// テストケースは `graph_adjacency_list_test.cpp` を参照 diff --git a/ja/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp b/ja/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp new file mode 100644 index 000000000..a447a1d4d --- /dev/null +++ b/ja/codes/cpp/chapter_graph/graph_adjacency_list_test.cpp @@ -0,0 +1,49 @@ +/** + * File: graph_adjacency_list_test.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com) + */ + +#include "./graph_adjacency_list.cpp" + +/* Driver Code */ +int main() { + /* 無向グラフを初期化 */ + vector v = valsToVets(vector{1, 3, 2, 5, 4}); + vector> edges = {{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, + {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}}; + GraphAdjList graph(edges); + cout << "\n初期化後、グラフは" << endl; + graph.print(); + + /* 辺を追加 */ + // 頂点 1, 2 は v[0], v[2] + graph.addEdge(v[0], v[2]); + cout << "\n辺 1-2 を追加した後、グラフは" << endl; + graph.print(); + + /* 辺を削除 */ + // 頂点 1, 3 は v[0], v[1] + graph.removeEdge(v[0], v[1]); + cout << "\n辺 1-3 を削除した後、グラフは" << endl; + graph.print(); + + /* 頂点を追加 */ + Vertex *v5 = new Vertex(6); + graph.addVertex(v5); + cout << "\n頂点 6 を追加した後、グラフは" << endl; + graph.print(); + + /* 頂点を削除 */ + // 頂点 3 は v[1] + graph.removeVertex(v[1]); + cout << "\n頂点 3 を削除した後、グラフは" << endl; + graph.print(); + + // メモリを解放する + for (Vertex *vet : v) { + delete vet; + } + + return 0; +} diff --git a/ja/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp b/ja/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp index 137bb4199..47923d19c 100644 --- a/ja/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp +++ b/ja/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp @@ -8,8 +8,8 @@ /* 隣接行列に基づく無向グラフクラス */ class GraphAdjMat { - vector vertices; // 頂点リスト、要素は「頂点値」を表し、インデックスは「頂点インデックス」を表す - vector> adjMat; // 隣接行列、行と列のインデックスは「頂点インデックス」に対応 + vector vertices; // 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す + vector> adjMat; // 隣接行列。行・列のインデックスは「頂点インデックス」に対応 public: /* コンストラクタ */ @@ -19,7 +19,7 @@ class GraphAdjMat { addVertex(val); } // 辺を追加 - // 辺の要素は頂点インデックスを表す + // 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する for (const vector &edge : edges) { addEdge(edge[0], edge[1]); } @@ -33,11 +33,11 @@ class GraphAdjMat { /* 頂点を追加 */ void addVertex(int val) { int n = size(); - // 頂点リストに新しい頂点値を追加 + // 頂点リストに新しい頂点の値を追加 vertices.push_back(val); - // 隣接行列に行を追加 + // 隣接行列に 1 行追加 adjMat.emplace_back(vector(n, 0)); - // 隣接行列に列を追加 + // 隣接行列に 1 列追加 for (vector &row : adjMat) { row.push_back(0); } @@ -46,42 +46,42 @@ class GraphAdjMat { /* 頂点を削除 */ void removeVertex(int index) { if (index >= size()) { - throw out_of_range("Vertex does not exist"); + throw out_of_range("頂点が存在しません"); } - // 頂点リストから`index`の頂点を削除 + // 頂点リストから index の頂点を削除する vertices.erase(vertices.begin() + index); - // 隣接行列から`index`の行を削除 + // 隣接行列で index 行を削除する adjMat.erase(adjMat.begin() + index); - // 隣接行列から`index`の列を削除 + // 隣接行列で index 列を削除する for (vector &row : adjMat) { row.erase(row.begin() + index); } } /* 辺を追加 */ - // パラメータi、jは頂点要素のインデックスに対応 + // 引数 i, j は vertices の要素インデックスに対応する void addEdge(int i, int j) { - // インデックス範囲外と等価性を処理 + // インデックスの範囲外と等値の処理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { - throw out_of_range("Vertex does not exist"); + throw out_of_range("頂点が存在しません"); } - // 無向グラフでは、隣接行列は主対角線について対称、即ち(i, j) == (j, i)を満たす + // 無向グラフでは、隣接行列は主対角線に関して対称、すなわち (i, j) == (j, i) を満たす adjMat[i][j] = 1; adjMat[j][i] = 1; } /* 辺を削除 */ - // パラメータi、jは頂点要素のインデックスに対応 + // 引数 i, j は vertices の要素インデックスに対応する void removeEdge(int i, int j) { - // インデックス範囲外と等価性を処理 + // インデックスの範囲外と等値の処理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { - throw out_of_range("Vertex does not exist"); + throw out_of_range("頂点が存在しません"); } adjMat[i][j] = 0; adjMat[j][i] = 0; } - /* 隣接行列を印刷 */ + /* 隣接行列を出力 */ void print() { cout << "頂点リスト = "; printVector(vertices); @@ -90,10 +90,10 @@ class GraphAdjMat { } }; -/* ドライバーコード */ +/* Driver Code */ int main() { /* 無向グラフを初期化 */ - // 辺の要素は頂点インデックスを表す + // edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意 vector vertices = {1, 3, 2, 5, 4}; vector> edges = {{0, 1}, {0, 3}, {1, 2}, {2, 3}, {2, 4}, {3, 4}}; GraphAdjMat graph(vertices, edges); @@ -101,27 +101,27 @@ int main() { graph.print(); /* 辺を追加 */ - // 頂点1、2のインデックスはそれぞれ0、2 + // 頂点 1, 2 のインデックスはそれぞれ 0, 2 graph.addEdge(0, 2); - cout << "\n辺 1-2 を追加後、グラフは" << endl; + cout << "\n辺 1-2 を追加した後、グラフは" << endl; graph.print(); /* 辺を削除 */ - // 頂点1、3のインデックスはそれぞれ0、1 + // 頂点 1, 3 のインデックスはそれぞれ 0, 1 graph.removeEdge(0, 1); - cout << "\n辺 1-3 を削除後、グラフは" << endl; + cout << "\n辺 1-3 を削除した後、グラフは" << endl; graph.print(); /* 頂点を追加 */ graph.addVertex(6); - cout << "\n頂点 6 を追加後、グラフは" << endl; + cout << "\n頂点 6 を追加した後、グラフは" << endl; graph.print(); /* 頂点を削除 */ - // 頂点3のインデックスは1 + // 頂点 3 のインデックスは 1 graph.removeVertex(1); - cout << "\n頂点 3 を削除後、グラフは" << endl; + cout << "\n頂点 3 を削除した後、グラフは" << endl; graph.print(); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_graph/graph_bfs.cpp b/ja/codes/cpp/chapter_graph/graph_bfs.cpp index 0e85a82f4..fa453e767 100644 --- a/ja/codes/cpp/chapter_graph/graph_bfs.cpp +++ b/ja/codes/cpp/chapter_graph/graph_bfs.cpp @@ -7,34 +7,34 @@ #include "../utils/common.hpp" #include "./graph_adjacency_list.cpp" -/* 幅優先走査 */ -// 隣接リストを使用してグラフを表現し、指定された頂点のすべての隣接頂点を取得 +/* 幅優先探索 */ +// グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする vector graphBFS(GraphAdjList &graph, Vertex *startVet) { - // 頂点走査順序 + // 頂点の走査順序 vector res; - // ハッシュセット、訪問済み頂点を記録するために使用 + // 訪問済み頂点を記録するためのハッシュ集合 unordered_set visited = {startVet}; - // BFSを実装するために使用されるキュー + // BFS の実装にキューを用いる queue que; que.push(startVet); - // 頂点vetから開始し、すべての頂点が訪問されるまでループ + // 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す while (!que.empty()) { Vertex *vet = que.front(); - que.pop(); // キューの先頭の頂点をデキュー - res.push_back(vet); // 訪問済み頂点を記録 - // その頂点のすべての隣接頂点を走査 + que.pop(); // 先頭の頂点をデキュー + res.push_back(vet); // 訪問した頂点を記録 + // この頂点のすべての隣接頂点を走査 for (auto adjVet : graph.adjList[vet]) { if (visited.count(adjVet)) - continue; // すでに訪問済みの頂点をスキップ - que.push(adjVet); // 未訪問の頂点のみをエンキュー - visited.emplace(adjVet); // 頂点を訪問済みとしてマーク + continue; // 訪問済みの頂点をスキップ + que.push(adjVet); // 未訪問の頂点のみをキューに追加 + visited.emplace(adjVet); // この頂点を訪問済みにする } } - // 頂点走査順序を返す + // 頂点の走査順を返す return res; } -/* ドライバーコード */ +/* Driver Code */ int main() { /* 無向グラフを初期化 */ vector v = valsToVets({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); @@ -42,18 +42,18 @@ int main() { {v[2], v[5]}, {v[3], v[4]}, {v[3], v[6]}, {v[4], v[5]}, {v[4], v[7]}, {v[5], v[8]}, {v[6], v[7]}, {v[7], v[8]}}; GraphAdjList graph(edges); - cout << "\n初期化後、グラフは\n"; + cout << "\n初期化後、グラフは\\n"; graph.print(); - /* 幅優先走査 */ + /* 幅優先探索 */ vector res = graphBFS(graph, v[0]); - cout << "\n幅優先走査(BFS)の頂点順序は" << endl; + cout << "\n幅優先探索(BFS)の頂点順序は" << endl; printVector(vetsToVals(res)); - // メモリを解放 + // メモリを解放する for (Vertex *vet : v) { delete vet; } return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_graph/graph_dfs.cpp b/ja/codes/cpp/chapter_graph/graph_dfs.cpp index 44ed1fc72..d5042b75d 100644 --- a/ja/codes/cpp/chapter_graph/graph_dfs.cpp +++ b/ja/codes/cpp/chapter_graph/graph_dfs.cpp @@ -7,31 +7,31 @@ #include "../utils/common.hpp" #include "./graph_adjacency_list.cpp" -/* 深さ優先走査ヘルパー関数 */ +/* 深さ優先走査の補助関数 */ void dfs(GraphAdjList &graph, unordered_set &visited, vector &res, Vertex *vet) { - res.push_back(vet); // 訪問済み頂点を記録 - visited.emplace(vet); // 頂点を訪問済みとしてマーク - // その頂点のすべての隣接頂点を走査 + res.push_back(vet); // 訪問した頂点を記録 + visited.emplace(vet); // この頂点を訪問済みにする + // この頂点のすべての隣接頂点を走査 for (Vertex *adjVet : graph.adjList[vet]) { if (visited.count(adjVet)) - continue; // すでに訪問済みの頂点をスキップ + continue; // 訪問済みの頂点をスキップ // 隣接頂点を再帰的に訪問 dfs(graph, visited, res, adjVet); } } -/* 深さ優先走査 */ -// 隣接リストを使用してグラフを表現し、指定された頂点のすべての隣接頂点を取得 +/* 深さ優先探索 */ +// グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする vector graphDFS(GraphAdjList &graph, Vertex *startVet) { - // 頂点走査順序 + // 頂点の走査順序 vector res; - // ハッシュセット、訪問済み頂点を記録するために使用 + // 訪問済み頂点を記録するためのハッシュ集合 unordered_set visited; dfs(graph, visited, res, startVet); return res; } -/* ドライバーコード */ +/* Driver Code */ int main() { /* 無向グラフを初期化 */ vector v = valsToVets(vector{0, 1, 2, 3, 4, 5, 6}); @@ -41,15 +41,15 @@ int main() { cout << "\n初期化後、グラフは" << endl; graph.print(); - /* 深さ優先走査 */ + /* 深さ優先探索 */ vector res = graphDFS(graph, v[0]); - cout << "\n深さ優先走査(DFS)の頂点順序は" << endl; + cout << "\n深さ優先探索(DFS)の頂点順序は" << endl; printVector(vetsToVals(res)); - // メモリを解放 + // メモリを解放する for (Vertex *vet : v) { delete vet; } return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_greedy/CMakeLists.txt b/ja/codes/cpp/chapter_greedy/CMakeLists.txt new file mode 100644 index 000000000..91788668d --- /dev/null +++ b/ja/codes/cpp/chapter_greedy/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(coin_change_greedy coin_change_greedy.cpp) +add_executable(fractional_knapsack fractional_knapsack.cpp) +add_executable(max_capacity max_capacity.cpp) \ No newline at end of file diff --git a/ja/codes/cpp/chapter_greedy/coin_change_greedy.cpp b/ja/codes/cpp/chapter_greedy/coin_change_greedy.cpp index abd112706..ae4d7cc9f 100644 --- a/ja/codes/cpp/chapter_greedy/coin_change_greedy.cpp +++ b/ja/codes/cpp/chapter_greedy/coin_change_greedy.cpp @@ -6,55 +6,55 @@ #include "../utils/common.hpp" -/* 硬貨両替:貪欲法 */ +/* コイン交換:貪欲法 */ int coinChangeGreedy(vector &coins, int amt) { - // 硬貨リストが順序付けされていると仮定 + // coins リストはソート済みと仮定する int i = coins.size() - 1; int count = 0; - // 残り金額がなくなるまで貪欲選択をループ + // 残額がなくなるまで貪欲選択を繰り返す while (amt > 0) { - // 残り金額に近く、それ以下の最小硬貨を見つける + // 残額以下で最も近い硬貨を見つける while (i > 0 && coins[i] > amt) { i--; } - // coins[i] を選択 + // coins[i] を選択する amt -= coins[i]; count++; } - // 実行可能な解が見つからない場合、-1 を返す + // 実行可能な解が見つからなければ -1 を返す return amt == 0 ? count : -1; } -/* ドライバーコード */ +/* Driver Code */ int main() { - // 貪欲法:大域最適解の発見を保証できる + // 貪欲法:大域最適解を保証できる vector coins = {1, 5, 10, 20, 50, 100}; int amt = 186; int res = coinChangeGreedy(coins, amt); cout << "\ncoins = "; printVector(coins); cout << "amt = " << amt << endl; - cout << amt << " を作るのに必要な最小硬貨数は " << res << " です" << endl; + cout << amt << " を作るのに必要な最小硬貨枚数は " << res << endl; - // 貪欲法:大域最適解の発見を保証できない + // 貪欲法:大域最適解を保証できない coins = {1, 20, 50}; amt = 60; res = coinChangeGreedy(coins, amt); cout << "\ncoins = "; printVector(coins); cout << "amt = " << amt << endl; - cout << amt << " を作るのに必要な最小硬貨数は " << res << " です" << endl; - cout << "実際には、最小必要数は 3 です。つまり、20 + 20 + 20" << endl; + cout << amt << " を作るのに必要な最小硬貨枚数は " << res << endl; + cout << "実際に必要な最小枚数は 3、つまり 20 + 20 + 20" << endl; - // 貪欲法:大域最適解の発見を保証できない + // 貪欲法:大域最適解を保証できない coins = {1, 49, 50}; amt = 98; res = coinChangeGreedy(coins, amt); cout << "\ncoins = "; printVector(coins); cout << "amt = " << amt << endl; - cout << amt << " を作るのに必要な最小硬貨数は " << res << " です" << endl; - cout << "実際には、最小必要数は 2 です。つまり、49 + 49" << endl; + cout << amt << " を作るのに必要な最小硬貨枚数は " << res << endl; + cout << "実際に必要な最小枚数は 2、つまり 49 + 49" << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_greedy/fractional_knapsack.cpp b/ja/codes/cpp/chapter_greedy/fractional_knapsack.cpp index 806722c5a..89635edb9 100644 --- a/ja/codes/cpp/chapter_greedy/fractional_knapsack.cpp +++ b/ja/codes/cpp/chapter_greedy/fractional_knapsack.cpp @@ -6,11 +6,11 @@ #include "../utils/common.hpp" -/* アイテム */ +/* 品物 */ class Item { public: - int w; // アイテムの重量 - int v; // アイテムの価値 + int w; // 品物の重さ + int v; // 品物の価値 Item(int w, int v) : w(w), v(v) { } @@ -18,39 +18,39 @@ class Item { /* 分数ナップサック:貪欲法 */ double fractionalKnapsack(vector &wgt, vector &val, int cap) { - // アイテムリストを作成、2つの属性を含む:重量、価値 + // 重さと価値の 2 属性を持つ品物リストを作成 vector items; for (int i = 0; i < wgt.size(); i++) { items.push_back(Item(wgt[i], val[i])); } - // 単位価値 item.v / item.w で高い順にソート + // 単位価値 item.v / item.w の高い順にソートする sort(items.begin(), items.end(), [](Item &a, Item &b) { return (double)a.v / a.w > (double)b.v / b.w; }); - // 貪欲選択をループ + // 貪欲選択を繰り返す double res = 0; for (auto &item : items) { if (item.w <= cap) { - // 残り容量が十分な場合、アイテム全体をナップサックに入れる + // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる res += item.v; cap -= item.w; } else { - // 残り容量が不十分な場合、アイテムの一部をナップサックに入れる + // 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる res += (double)item.v / item.w * cap; - // 残り容量がなくなったため、ループを中断 + // 残り容量がないため、ループを抜ける break; } } return res; } -/* ドライバーコード */ +/* Driver Code */ int main() { vector wgt = {10, 20, 30, 40, 50}; vector val = {50, 120, 150, 210, 240}; int cap = 50; - // 貪欲アルゴリズム + // 貪欲法 double res = fractionalKnapsack(wgt, val, cap); - cout << "ナップサック容量内での最大値は " << res << " です" << endl; + cout << "ナップサック容量を超えない最大価値は " << res << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_greedy/max_capacity.cpp b/ja/codes/cpp/chapter_greedy/max_capacity.cpp index b8fbf8a0b..0feba1555 100644 --- a/ja/codes/cpp/chapter_greedy/max_capacity.cpp +++ b/ja/codes/cpp/chapter_greedy/max_capacity.cpp @@ -8,16 +8,16 @@ /* 最大容量:貪欲法 */ int maxCapacity(vector &ht) { - // i、j を初期化し、配列の両端で分割させる + // i, j を初期化し、それぞれ配列の両端に置く int i = 0, j = ht.size() - 1; - // 初期最大容量は 0 + // 初期の最大容量は 0 int res = 0; - // 2つの板が出会うまで貪欲選択をループ + // 2 枚の板が出会うまで貪欲選択を繰り返す while (i < j) { - // 最大容量を更新 + // 最大容量を更新する int cap = min(ht[i], ht[j]) * (j - i); res = max(res, cap); - // より短い板を内側に移動 + // 短い方を内側へ動かす if (ht[i] < ht[j]) { i++; } else { @@ -27,13 +27,13 @@ int maxCapacity(vector &ht) { return res; } -/* ドライバーコード */ +/* Driver Code */ int main() { vector ht = {3, 8, 5, 2, 7, 7, 3, 4}; - // 貪欲アルゴリズム + // 貪欲法 int res = maxCapacity(ht); - cout << "最大容量は " << res << " です" << endl; + cout << "最大容量は " << res << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_greedy/max_product_cutting.cpp b/ja/codes/cpp/chapter_greedy/max_product_cutting.cpp index b35248656..296b9682b 100644 --- a/ja/codes/cpp/chapter_greedy/max_product_cutting.cpp +++ b/ja/codes/cpp/chapter_greedy/max_product_cutting.cpp @@ -6,34 +6,34 @@ #include "../utils/common.hpp" -/* 最大積切断:貪欲法 */ +/* 最大切断積:貪欲法 */ int maxProductCutting(int n) { - // n <= 3 の場合、1 を切り出す必要がある + // n <= 3 のときは、必ず 1 を切り出す if (n <= 3) { return 1 * (n - 1); } - // 貪欲に 3 を切り出す。a は 3 の個数、b は余り + // 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする int a = n / 3; int b = n % 3; if (b == 1) { - // 余りが 1 の場合、1 * 3 のペアを 2 * 2 に変換 + // 余りが 1 のときは、1 * 3 を 2 * 2 に変える return (int)pow(3, a - 1) * 2 * 2; } if (b == 2) { - // 余りが 2 の場合、何もしない + // 余りが 2 のときは、そのままにする return (int)pow(3, a) * 2; } - // 余りが 0 の場合、何もしない + // 余りが 0 のときは、そのままにする return (int)pow(3, a); } -/* ドライバーコード */ +/* Driver Code */ int main() { int n = 58; - // 貪欲アルゴリズム + // 貪欲法 int res = maxProductCutting(n); - cout << "分割の最大積は " << res << " です" << endl; + cout << "最大分割積は" << res << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_hashing/CMakeLists.txt b/ja/codes/cpp/chapter_hashing/CMakeLists.txt new file mode 100644 index 000000000..6b583ef55 --- /dev/null +++ b/ja/codes/cpp/chapter_hashing/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(hash_map hash_map.cpp) +add_executable(array_hash_map_test array_hash_map_test.cpp) +add_executable(hash_map_chaining hash_map_chaining.cpp) +add_executable(hash_map_open_addressing hash_map_open_addressing.cpp) +add_executable(simple_hash simple_hash.cpp) +add_executable(built_in_hash built_in_hash.cpp) \ No newline at end of file diff --git a/ja/codes/cpp/chapter_hashing/array_hash_map.cpp b/ja/codes/cpp/chapter_hashing/array_hash_map.cpp index 182c14e18..c9c534948 100644 --- a/ja/codes/cpp/chapter_hashing/array_hash_map.cpp +++ b/ja/codes/cpp/chapter_hashing/array_hash_map.cpp @@ -6,7 +6,7 @@ #include "../utils/common.hpp" -/* キー値ペア */ +/* キーと値の組 */ struct Pair { public: int key; @@ -17,19 +17,19 @@ struct Pair { } }; -/* 配列実装に基づくハッシュテーブル */ +/* 配列ベースのハッシュテーブル */ class ArrayHashMap { private: vector buckets; public: ArrayHashMap() { - // 配列を初期化、100個のバケットを含む + // 100 個のバケットを含む配列を初期化 buckets = vector(100); } ~ArrayHashMap() { - // メモリを解放 + // メモリを解放する for (const auto &bucket : buckets) { delete bucket; } @@ -42,7 +42,7 @@ class ArrayHashMap { return index; } - /* クエリ操作 */ + /* 検索操作 */ string get(int key) { int index = hashFunc(key); Pair *pair = buckets[index]; @@ -61,12 +61,12 @@ class ArrayHashMap { /* 削除操作 */ void remove(int key) { int index = hashFunc(key); - // メモリを解放してnullptrに設定 + // メモリを解放して nullptr に設定する delete buckets[index]; buckets[index] = nullptr; } - /* すべてのキー値ペアを取得 */ + /* すべてのキーと値のペアを取得 */ vector pairSet() { vector pairSet; for (Pair *pair : buckets) { @@ -99,7 +99,7 @@ class ArrayHashMap { return valueSet; } - /* ハッシュテーブルを印刷 */ + /* ハッシュテーブルを出力 */ void print() { for (Pair *kv : pairSet()) { cout << kv->key << " -> " << kv->val << endl; @@ -107,4 +107,4 @@ class ArrayHashMap { } }; -// テストケースはarray_hash_map_test.cppを参照 \ No newline at end of file +// テストケースは `array_hash_map_test.cpp` を参照 diff --git a/ja/codes/cpp/chapter_hashing/array_hash_map_test.cpp b/ja/codes/cpp/chapter_hashing/array_hash_map_test.cpp index c8a7eb676..bff8d7289 100644 --- a/ja/codes/cpp/chapter_hashing/array_hash_map_test.cpp +++ b/ja/codes/cpp/chapter_hashing/array_hash_map_test.cpp @@ -6,47 +6,47 @@ #include "./array_hash_map.cpp" -/* ドライバーコード */ +/* Driver Code */ int main() { /* ハッシュテーブルを初期化 */ ArrayHashMap map = ArrayHashMap(); /* 追加操作 */ - // キー値ペア(key, value)をハッシュテーブルに追加 - map.put(12836, "Ha"); - map.put(15937, "Luo"); - map.put(16750, "Suan"); - map.put(13276, "Fa"); - map.put(10583, "Ya"); - cout << "\nAfter adding, the hash table is\nKey -> Value" << endl; + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.put(12836, "シャオハー"); + map.put(15937, "シャオルオ"); + map.put(16750, "シャオスワン"); + map.put(13276, "シャオファー"); + map.put(10583, "シャオヤー"); + cout << "\n追加完了後、ハッシュテーブルは\nKey -> Value" << endl; map.print(); - /* クエリ操作 */ - // ハッシュテーブルにキーを入力、値を取得 + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 string name = map.get(15937); - cout << "\nEnter student ID 15937, found name " << name << endl; + cout << "\n学籍番号 15937 を入力すると、氏名 " << name << endl; /* 削除操作 */ - // ハッシュテーブルからキー値ペア(key, value)を削除 + // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(10583); - cout << "\nAfter removing 10583, the hash table is\nKey -> Value" << endl; + cout << "\n10583 を削除した後、ハッシュテーブルは\nKey -> Value" << endl; map.print(); /* ハッシュテーブルを走査 */ - cout << "\nTraverse key-value pairs Key->Value" << endl; + cout << "\nキーと値のペア Key->Value を走査" << endl; for (auto kv : map.pairSet()) { cout << kv->key << " -> " << kv->val << endl; } - cout << "\nIndividually traverse keys Key" << endl; + cout << "\nキー Key のみを走査" << endl; for (auto key : map.keySet()) { cout << key << endl; } - cout << "\nIndividually traverse values Value" << endl; + cout << "\n値 Value のみを走査" << endl; for (auto val : map.valueSet()) { cout << val << endl; } return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_hashing/built_in_hash.cpp b/ja/codes/cpp/chapter_hashing/built_in_hash.cpp index 086d1ec9b..ce5d9665f 100644 --- a/ja/codes/cpp/chapter_hashing/built_in_hash.cpp +++ b/ja/codes/cpp/chapter_hashing/built_in_hash.cpp @@ -6,24 +6,24 @@ #include "../utils/common.hpp" -/* ドライバーコード */ +/* Driver Code */ int main() { int num = 3; size_t hashNum = hash()(num); - cout << "The hash value of integer " << num << " is " << hashNum << "\n"; + cout << "整数 " << num << " のハッシュ値は " << hashNum << "\n"; bool bol = true; size_t hashBol = hash()(bol); - cout << "The hash value of boolean " << bol << " is " << hashBol << "\n"; + cout << "真偽値 " << bol << " のハッシュ値は " << hashBol << "\n"; double dec = 3.14159; size_t hashDec = hash()(dec); - cout << "The hash value of decimal " << dec << " is " << hashDec << "\n"; + cout << "小数 " << dec << " のハッシュ値は " << hashDec << "\n"; - string str = "Hello algorithm"; + string str = "Hello アルゴリズム"; size_t hashStr = hash()(str); - cout << "The hash value of string " << str << " is " << hashStr << "\n"; + cout << "文字列 " << str << " のハッシュ値は " << hashStr << "\n"; - // C++では、組み込みのstd:hash()は基本データ型のハッシュ値のみを提供 - // 配列やオブジェクトのハッシュ値計算は手動で実装する必要がある -} \ No newline at end of file + // C++ では、組み込みの std::hash() は基本データ型のハッシュ値計算しか提供しない + // 配列やオブジェクトのハッシュ値計算は自分で実装する必要がある +} diff --git a/ja/codes/cpp/chapter_hashing/hash_map.cpp b/ja/codes/cpp/chapter_hashing/hash_map.cpp index 05a9c8018..c1fb370b4 100644 --- a/ja/codes/cpp/chapter_hashing/hash_map.cpp +++ b/ja/codes/cpp/chapter_hashing/hash_map.cpp @@ -6,41 +6,41 @@ #include "../utils/common.hpp" -/* ドライバーコード */ +/* Driver Code */ int main() { /* ハッシュテーブルを初期化 */ unordered_map map; /* 追加操作 */ - // キー値ペア(key, value)をハッシュテーブルに追加 - map[12836] = "Ha"; - map[15937] = "Luo"; - map[16750] = "Suan"; - map[13276] = "Fa"; - map[10583] = "Ya"; - cout << "\nAfter adding, the hash table is\nKey -> Value" << endl; + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map[12836] = "シャオハー"; + map[15937] = "シャオルオ"; + map[16750] = "シャオスワン"; + map[13276] = "シャオファー"; + map[10583] = "シャオヤー"; + cout << "\n追加完了後、ハッシュテーブルは\nKey -> Value" << endl; printHashMap(map); - /* クエリ操作 */ - // ハッシュテーブルにキーを入力、値を取得 + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 string name = map[15937]; - cout << "\nEnter student ID 15937, found name " << name << endl; + cout << "\n学籍番号 15937 を入力すると、氏名 " << name << endl; /* 削除操作 */ - // ハッシュテーブルからキー値ペア(key, value)を削除 + // ハッシュテーブルからキーと値のペア (key, value) を削除 map.erase(10583); - cout << "\nAfter removing 10583, the hash table is\nKey -> Value" << endl; + cout << "\n10583 を削除した後、ハッシュテーブルは\nKey -> Value" << endl; printHashMap(map); /* ハッシュテーブルを走査 */ - cout << "\nTraverse key-value pairs Key->Value" << endl; + cout << "\nキーと値のペア Key->Value を走査" << endl; for (auto kv : map) { cout << kv.first << " -> " << kv.second << endl; } - cout << "\nIterate through Key->Value using an iterator" << endl; + cout << "\nイテレータで Key->Value を走査" << endl; for (auto iter = map.begin(); iter != map.end(); iter++) { cout << iter->first << "->" << iter->second << endl; } return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_hashing/hash_map_chaining.cpp b/ja/codes/cpp/chapter_hashing/hash_map_chaining.cpp index c920d05b1..ff442c4e3 100644 --- a/ja/codes/cpp/chapter_hashing/hash_map_chaining.cpp +++ b/ja/codes/cpp/chapter_hashing/hash_map_chaining.cpp @@ -9,9 +9,9 @@ /* チェイン法ハッシュテーブル */ class HashMapChaining { private: - int size; // キー値ペアの数 - int capacity; // ハッシュテーブルの容量 - double loadThres; // 拡張をトリガーする負荷率の閾値 + int size; // キーと値のペア数 + int capacity; // ハッシュテーブル容量 + double loadThres; // リサイズを発動する負荷率のしきい値 int extendRatio; // 拡張倍率 vector> buckets; // バケット配列 @@ -21,11 +21,11 @@ class HashMapChaining { buckets.resize(capacity); } - /* デストラクタ */ + /* デストラクタメソッド */ ~HashMapChaining() { for (auto &bucket : buckets) { for (Pair *pair : bucket) { - // メモリを解放 + // メモリを解放する delete pair; } } @@ -41,34 +41,34 @@ class HashMapChaining { return (double)size / (double)capacity; } - /* クエリ操作 */ + /* 検索操作 */ string get(int key) { int index = hashFunc(key); - // バケットを走査、キーが見つかった場合、対応するvalを返却 + // バケットを走査し、key が見つかれば対応する val を返す for (Pair *pair : buckets[index]) { if (pair->key == key) { return pair->val; } } - // キーが見つからない場合、空文字列を返却 + // key が見つからない場合は空文字列を返す return ""; } /* 追加操作 */ void put(int key, string val) { - // 負荷率が閾値を超えた場合、拡張を実行 + // 負荷率がしきい値を超えたら、リサイズを実行 if (loadFactor() > loadThres) { extend(); } int index = hashFunc(key); - // バケットを走査、指定キーに遭遇した場合、対応するvalを更新して返却 + // バケットを走査し、指定した key が見つかれば対応する val を更新して返す for (Pair *pair : buckets[index]) { if (pair->key == key) { pair->val = val; return; } } - // キーが見つからない場合、キー値ペアを末尾に追加 + // その key が存在しなければ、キーと値のペアを末尾に追加 buckets[index].push_back(new Pair(key, val)); size++; } @@ -77,12 +77,12 @@ class HashMapChaining { void remove(int key) { int index = hashFunc(key); auto &bucket = buckets[index]; - // バケットを走査、キー値ペアを削除 + // バケットを走査してキーと値のペアを削除 for (int i = 0; i < bucket.size(); i++) { if (bucket[i]->key == key) { Pair *tmp = bucket[i]; - bucket.erase(bucket.begin() + i); // キー値ペアを削除 - delete tmp; // メモリを解放 + bucket.erase(bucket.begin() + i); // そこからキーと値の組を削除する + delete tmp; // メモリを解放する size--; return; } @@ -93,22 +93,22 @@ class HashMapChaining { void extend() { // 元のハッシュテーブルを一時保存 vector> bucketsTmp = buckets; - // 拡張された新しいハッシュテーブルを初期化 + // リサイズ後の新しいハッシュテーブルを初期化 capacity *= extendRatio; buckets.clear(); buckets.resize(capacity); size = 0; - // 元のハッシュテーブルから新しいハッシュテーブルにキー値ペアを移動 + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for (auto &bucket : bucketsTmp) { for (Pair *pair : bucket) { put(pair->key, pair->val); - // メモリを解放 + // メモリを解放する delete pair; } } } - /* ハッシュテーブルを印刷 */ + /* ハッシュテーブルを出力 */ void print() { for (auto &bucket : buckets) { cout << "["; @@ -120,31 +120,31 @@ class HashMapChaining { } }; -/* ドライバーコード */ +/* Driver Code */ int main() { /* ハッシュテーブルを初期化 */ HashMapChaining map = HashMapChaining(); /* 追加操作 */ - // キー値ペア(key, value)をハッシュテーブルに追加 - map.put(12836, "Ha"); - map.put(15937, "Luo"); - map.put(16750, "Suan"); - map.put(13276, "Fa"); - map.put(10583, "Ya"); - cout << "\nAfter adding, the hash table is\nKey -> Value" << endl; + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.put(12836, "シャオハー"); + map.put(15937, "シャオルオ"); + map.put(16750, "シャオスワン"); + map.put(13276, "シャオファー"); + map.put(10583, "シャオヤー"); + cout << "\n追加完了後、ハッシュテーブルは\nKey -> Value" << endl; map.print(); - /* クエリ操作 */ - // ハッシュテーブルにキーを入力、値を取得 + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 string name = map.get(13276); - cout << "\nEnter student ID 13276, found name " << name << endl; + cout << "\n学籍番号 13276 を入力すると、氏名 " << name << endl; /* 削除操作 */ - // ハッシュテーブルからキー値ペア(key, value)を削除 + // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(12836); - cout << "\nAfter removing 12836, the hash table is\nKey -> Value" << endl; + cout << "\n12836 を削除した後、ハッシュテーブルは\nKey -> Value" << endl; map.print(); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp b/ja/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp index 0c96eb6f5..96e2b9f28 100644 --- a/ja/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp +++ b/ja/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp @@ -9,19 +9,19 @@ /* オープンアドレス法ハッシュテーブル */ class HashMapOpenAddressing { private: - int size; // キー値ペアの数 - int capacity = 4; // ハッシュテーブルの容量 - const double loadThres = 2.0 / 3.0; // 拡張をトリガーする負荷率の閾値 + int size; // キーと値のペア数 + int capacity = 4; // ハッシュテーブル容量 + const double loadThres = 2.0 / 3.0; // リサイズを発動する負荷率のしきい値 const int extendRatio = 2; // 拡張倍率 vector buckets; // バケット配列 - Pair *TOMBSTONE = new Pair(-1, "-1"); // 削除マーク + Pair *TOMBSTONE = new Pair(-1, "-1"); // 削除済みマーク public: /* コンストラクタ */ HashMapOpenAddressing() : size(0), buckets(capacity, nullptr) { } - /* デストラクタ */ + /* デストラクタメソッド */ ~HashMapOpenAddressing() { for (Pair *pair : buckets) { if (pair != nullptr && pair != TOMBSTONE) { @@ -41,68 +41,68 @@ class HashMapOpenAddressing { return (double)size / capacity; } - /* keyに対応するバケットインデックスを検索 */ + /* key に対応するバケットインデックスを探す */ int findBucket(int key) { int index = hashFunc(key); int firstTombstone = -1; - // 線形探査、空のバケットに遭遇したら中断 + // 線形プロービングを行い、空バケットに達したら終了 while (buckets[index] != nullptr) { - // keyに遭遇した場合、対応するバケットインデックスを返却 + // key が見つかったら、対応するバケットのインデックスを返す if (buckets[index]->key == key) { - // 以前に削除マークに遭遇していた場合、キー値ペアをそのインデックスに移動 + // 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 if (firstTombstone != -1) { buckets[firstTombstone] = buckets[index]; buckets[index] = TOMBSTONE; - return firstTombstone; // 移動されたバケットインデックスを返却 + return firstTombstone; // 移動後のバケットインデックスを返す } - return index; // バケットインデックスを返却 + return index; // バケットのインデックスを返す } - // 最初に遭遇した削除マークを記録 + // 最初に見つかった削除マークを記録 if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { firstTombstone = index; } - // バケットインデックスを計算、末尾を超えた場合は先頭に戻る + // バケットのインデックスを計算し、末尾を越えたら先頭に戻る index = (index + 1) % capacity; } - // keyが存在しない場合、挿入ポイントのインデックスを返却 + // key が存在しない場合は追加位置のインデックスを返す return firstTombstone == -1 ? index : firstTombstone; } - /* クエリ操作 */ + /* 検索操作 */ string get(int key) { - // keyに対応するバケットインデックスを検索 + // key に対応するバケットインデックスを探す int index = findBucket(key); - // キー値ペアが見つかった場合、対応するvalを返却 + // キーと値の組が見つかったら、対応する val を返す if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { return buckets[index]->val; } - // キー値ペアが存在しない場合、空文字列を返却 + // キーと値の組が存在しない場合は空文字列を返す return ""; } /* 追加操作 */ void put(int key, string val) { - // 負荷率が閾値を超えた場合、拡張を実行 + // 負荷率がしきい値を超えたら、リサイズを実行 if (loadFactor() > loadThres) { extend(); } - // keyに対応するバケットインデックスを検索 + // key に対応するバケットインデックスを探す int index = findBucket(key); - // キー値ペアが見つかった場合、valを上書きして返却 + // キーと値の組が見つかったら、val を上書きして返す if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { buckets[index]->val = val; return; } - // キー値ペアが存在しない場合、キー値ペアを追加 + // キーと値の組が存在しない場合は、その組を追加する buckets[index] = new Pair(key, val); size++; } /* 削除操作 */ void remove(int key) { - // keyに対応するバケットインデックスを検索 + // key に対応するバケットインデックスを探す int index = findBucket(key); - // キー値ペアが見つかった場合、削除マークで覆う + // キーと値の組が見つかったら、削除マーカーで上書きする if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { delete buckets[index]; buckets[index] = TOMBSTONE; @@ -114,11 +114,11 @@ class HashMapOpenAddressing { void extend() { // 元のハッシュテーブルを一時保存 vector bucketsTmp = buckets; - // 拡張された新しいハッシュテーブルを初期化 + // リサイズ後の新しいハッシュテーブルを初期化 capacity *= extendRatio; buckets = vector(capacity, nullptr); size = 0; - // 元のハッシュテーブルから新しいハッシュテーブルにキー値ペアを移動 + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for (Pair *pair : bucketsTmp) { if (pair != nullptr && pair != TOMBSTONE) { put(pair->key, pair->val); @@ -127,7 +127,7 @@ class HashMapOpenAddressing { } } - /* ハッシュテーブルを印刷 */ + /* ハッシュテーブルを出力 */ void print() { for (Pair *pair : buckets) { if (pair == nullptr) { @@ -141,31 +141,31 @@ class HashMapOpenAddressing { } }; -/* ドライバーコード */ +/* Driver Code */ int main() { // ハッシュテーブルを初期化 HashMapOpenAddressing hashmap; // 追加操作 - // キー値ペア(key, val)をハッシュテーブルに追加 - hashmap.put(12836, "Ha"); - hashmap.put(15937, "Luo"); - hashmap.put(16750, "Suan"); - hashmap.put(13276, "Fa"); - hashmap.put(10583, "Ya"); - cout << "\nAfter adding, the hash table is\nKey -> Value" << endl; + // ハッシュテーブルにキーと値の組 (key, val) を追加する + hashmap.put(12836, "シャオハー"); + hashmap.put(15937, "シャオルオ"); + hashmap.put(16750, "シャオスワン"); + hashmap.put(13276, "シャオファー"); + hashmap.put(10583, "シャオヤー"); + cout << "\n追加完了後、ハッシュテーブルは\nKey -> Value" << endl; hashmap.print(); - // クエリ操作 - // ハッシュテーブルにキーを入力、値valを取得 + // 検索操作 + // ハッシュテーブルにキー key を入力し、値 val を得る string name = hashmap.get(13276); - cout << "\nEnter student ID 13276, found name " << name << endl; + cout << "\n学籍番号 13276 を入力すると、氏名 " << name << endl; // 削除操作 - // ハッシュテーブルからキー値ペア(key, val)を削除 + // ハッシュテーブルからキーと値の組 (key, val) を削除する hashmap.remove(16750); - cout << "\nAfter removing 16750, the hash table is\nKey -> Value" << endl; + cout << "\n16750 を削除した後、ハッシュテーブルは\nKey -> Value" << endl; hashmap.print(); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_hashing/simple_hash.cpp b/ja/codes/cpp/chapter_hashing/simple_hash.cpp index 7ae6e7302..a3b160749 100644 --- a/ja/codes/cpp/chapter_hashing/simple_hash.cpp +++ b/ja/codes/cpp/chapter_hashing/simple_hash.cpp @@ -26,7 +26,7 @@ int mulHash(string key) { return (int)hash; } -/* XORハッシュ */ +/* XOR ハッシュ */ int xorHash(string key) { int hash = 0; const int MODULUS = 1000000007; @@ -46,21 +46,21 @@ int rotHash(string key) { return (int)hash; } -/* ドライバーコード */ +/* Driver Code */ int main() { - string key = "Hello algorithm"; + string key = "Hello アルゴリズム"; int hash = addHash(key); - cout << "Additive hash value is " << hash << endl; + cout << "加算ハッシュ値は " << hash << endl; hash = mulHash(key); - cout << "Multiplicative hash value is " << hash << endl; + cout << "乗算ハッシュ値は " << hash << endl; hash = xorHash(key); - cout << "XOR hash value is " << hash << endl; + cout << "XORハッシュ値は " << hash << endl; hash = rotHash(key); - cout << "Rotational hash value is " << hash << endl; + cout << "回転ハッシュ値は " << hash << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_heap/CMakeLists.txt b/ja/codes/cpp/chapter_heap/CMakeLists.txt new file mode 100644 index 000000000..1ac33a44f --- /dev/null +++ b/ja/codes/cpp/chapter_heap/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(heap heap.cpp) +add_executable(my_heap my_heap.cpp) +add_executable(top_k top_k.cpp) diff --git a/ja/codes/cpp/chapter_heap/heap.cpp b/ja/codes/cpp/chapter_heap/heap.cpp index ea787385c..36788b6f8 100644 --- a/ja/codes/cpp/chapter_heap/heap.cpp +++ b/ja/codes/cpp/chapter_heap/heap.cpp @@ -7,40 +7,40 @@ #include "../utils/common.hpp" void testPush(priority_queue &heap, int val) { - heap.push(val); // 要素をヒープにプッシュ - cout << "\n要素 " << val << " をヒープに追加後" << endl; + heap.push(val); // 要素をヒープに追加 + cout << "\n要素 " << val << " をヒープに追加した後" << endl; printHeap(heap); } void testPop(priority_queue &heap) { int val = heap.top(); heap.pop(); - cout << "\nヒープから先頭要素 " << val << " を削除後" << endl; + cout << "\nヒープ先頭要素 " << val << " を取り出した後" << endl; printHeap(heap); } -/* ドライバーコード */ +/* Driver Code */ int main() { /* ヒープを初期化 */ - // 最小ヒープを初期化 + // 最小ヒープを初期化する // priority_queue, greater> minHeap; - // 最大ヒープを初期化 + // 最大ヒープを初期化する priority_queue, less> maxHeap; - cout << "\n以下のテストケースは最大ヒープ用です" << endl; + cout << "\n以下のテスト例は最大ヒープです" << endl; - /* ヒープに要素をプッシュ */ + /* 要素をヒープに追加 */ testPush(maxHeap, 1); testPush(maxHeap, 3); testPush(maxHeap, 2); testPush(maxHeap, 5); testPush(maxHeap, 4); - /* ヒープの先頭要素にアクセス */ + /* ヒープ頂点の要素を取得 */ int peek = maxHeap.top(); - cout << "\nヒープの先頭要素は " << peek << endl; + cout << "\nヒープ先頭要素は " << peek << endl; - /* ヒープ先頭の要素をポップ */ + /* ヒープ頂点の要素を取り出す */ testPop(maxHeap); testPop(maxHeap); testPop(maxHeap); @@ -49,18 +49,18 @@ int main() { /* ヒープのサイズを取得 */ int size = maxHeap.size(); - cout << "\nヒープ内の要素数は " << size << endl; + cout << "\nヒープ要素数は " << size << endl; - /* ヒープが空かどうか判定 */ + /* ヒープが空かどうかを判定 */ bool isEmpty = maxHeap.empty(); - cout << "\nヒープが空かどうか " << isEmpty << endl; + cout << "\nヒープが空かどうかは " << isEmpty << endl; /* リストを入力してヒープを構築 */ - // 時間計算量はO(n)、O(nlogn)ではない + // 時間計算量は O(n) であり、O(nlogn) ではない vector input{1, 3, 2, 5, 4}; priority_queue, greater> minHeap(input.begin(), input.end()); - cout << "リストを入力して最小ヒープを構築後" << endl; + cout << "リストを入力して最小ヒープを構築した後" << endl; printHeap(minHeap); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_heap/my_heap.cpp b/ja/codes/cpp/chapter_heap/my_heap.cpp index 8022f8925..b0a94af38 100644 --- a/ja/codes/cpp/chapter_heap/my_heap.cpp +++ b/ja/codes/cpp/chapter_heap/my_heap.cpp @@ -9,63 +9,63 @@ /* 最大ヒープ */ class MaxHeap { private: - // 動的配列を使用してサイズ変更の必要性を回避 + // 動的配列を使うことで、拡張を考慮せずに済む vector maxHeap; - /* 左の子ノードのインデックスを取得 */ + /* 左子ノードのインデックスを取得 */ int left(int i) { return 2 * i + 1; } - /* 右の子ノードのインデックスを取得 */ + /* 右子ノードのインデックスを取得 */ int right(int i) { return 2 * i + 2; } /* 親ノードのインデックスを取得 */ int parent(int i) { - return (i - 1) / 2; // 整数除算で切り下げ + return (i - 1) / 2; // 切り捨て除算 } - /* ノードiから上向きにヒープ化を開始 */ + /* ノード i から始めて、下から上へヒープ化 */ void siftUp(int i) { while (true) { - // ノードiの親ノードを取得 + // ノード i の親ノードを取得 int p = parent(i); - // 「ルートノードを超える」または「ノードが修復不要」の場合、ヒープ化を終了 + // 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 if (p < 0 || maxHeap[i] <= maxHeap[p]) break; - // 2つのノードを交換 + // 2 つのノードを交換 swap(maxHeap[i], maxHeap[p]); - // 上向きにループしてヒープ化 + // ループで下から上へヒープ化 i = p; } } - /* ノードiから下向きにヒープ化を開始 */ + /* ノード i から始めて、上から下へヒープ化 */ void siftDown(int i) { while (true) { - // i、l、rの中で最大のノードを決定し、maとして記録 + // ノード i, l, r のうち値が最大のノードを ma とする int l = left(i), r = right(i), ma = i; if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l; if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r; - // ノードiが最大、またはインデックスl、rが範囲外の場合、これ以上のヒープ化は不要、ブレーク + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if (ma == i) break; swap(maxHeap[i], maxHeap[ma]); - // 下向きにループしてヒープ化 + // ループで上から下へヒープ化 i = ma; } } public: - /* コンストラクタ、入力リストに基づいてヒープを構築 */ + /* コンストラクタ。入力リストに基づいてヒープを構築する */ MaxHeap(vector nums) { - // すべてのリスト要素をヒープに追加 + // リスト要素をそのままヒープに追加 maxHeap = nums; - // 葉以外のすべてのノードをヒープ化 + // 葉ノード以外のすべてのノードをヒープ化 for (int i = parent(size() - 1); i >= 0; i--) { siftDown(i); } @@ -76,17 +76,17 @@ class MaxHeap { return maxHeap.size(); } - /* ヒープが空かどうか判定 */ + /* ヒープが空かどうかを判定 */ bool isEmpty() { return size() == 0; } - /* ヒープの先頭要素にアクセス */ + /* ヒープ先頭要素にアクセス */ int peek() { return maxHeap[0]; } - /* ヒープに要素をプッシュ */ + /* 要素をヒープに追加 */ void push(int val) { // ノードを追加 maxHeap.push_back(val); @@ -94,13 +94,13 @@ class MaxHeap { siftUp(size() - 1); } - /* 要素がヒープから退出 */ + /* 要素をヒープから取り出す */ void pop() { - // 空の処理 + // 空判定の処理 if (isEmpty()) { - throw out_of_range("Heap is empty"); + throw out_of_range("ヒープが空です"); } - // ルートノードを最も右の葉ノードと交換(最初の要素と最後の要素を交換) + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) swap(maxHeap[0], maxHeap[size() - 1]); // ノードを削除 maxHeap.pop_back(); @@ -108,48 +108,48 @@ class MaxHeap { siftDown(0); } - /* ヒープを印刷(二分木)*/ + /* ヒープ(二分木)を出力 */ void print() { - cout << "ヒープの配列表現:"; + cout << "ヒープの配列表現:"; printVector(maxHeap); - cout << "ヒープの木表現:" << endl; + cout << "ヒープの木構造表現:" << endl; TreeNode *root = vectorToTree(maxHeap); printTree(root); freeMemoryTree(root); } }; -/* ドライバーコード */ +/* Driver Code */ int main() { /* 最大ヒープを初期化 */ vector vec{9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}; MaxHeap maxHeap(vec); - cout << "\nリストを入力してヒープを構築" << endl; + cout << "\nリストを入力してヒープを構築した後" << endl; maxHeap.print(); - /* ヒープの先頭要素にアクセス */ + /* ヒープ頂点の要素を取得 */ int peek = maxHeap.peek(); - cout << "\nヒープの先頭要素は " << peek << endl; + cout << "\nヒープ先頭要素は " << peek << endl; - /* ヒープに要素をプッシュ */ + /* 要素をヒープに追加 */ int val = 7; maxHeap.push(val); - cout << "\n要素 " << val << " をヒープに追加後" << endl; + cout << "\n要素 " << val << " をヒープに追加した後" << endl; maxHeap.print(); - /* ヒープ先頭の要素をポップ */ + /* ヒープ頂点の要素を取り出す */ peek = maxHeap.peek(); maxHeap.pop(); - cout << "\nヒープから先頭要素 " << peek << " を削除後" << endl; + cout << "\nヒープの先頭要素 " << peek << " を取り出した後" << endl; maxHeap.print(); /* ヒープのサイズを取得 */ int size = maxHeap.size(); - cout << "\nヒープ内の要素数は " << size << endl; + cout << "\nヒープ要素数は " << size << endl; - /* ヒープが空かどうか判定 */ + /* ヒープが空かどうかを判定 */ bool isEmpty = maxHeap.isEmpty(); - cout << "\nヒープが空かどうか " << isEmpty << endl; + cout << "\nヒープが空かどうかは " << isEmpty << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_heap/top_k.cpp b/ja/codes/cpp/chapter_heap/top_k.cpp index 96bd8ea68..eb5736a48 100644 --- a/ja/codes/cpp/chapter_heap/top_k.cpp +++ b/ja/codes/cpp/chapter_heap/top_k.cpp @@ -6,17 +6,17 @@ #include "../utils/common.hpp" -/* ヒープを使用して配列内の最大k個の要素を見つける */ +/* ヒープに基づいて配列中の最大の k 個の要素を探す */ priority_queue, greater> topKHeap(vector &nums, int k) { // 最小ヒープを初期化 priority_queue, greater> heap; - // 配列の最初のk個の要素をヒープに入力 + // 配列の先頭 k 個の要素をヒープに追加 for (int i = 0; i < k; i++) { heap.push(nums[i]); } - // k+1番目の要素から、ヒープの長さをkに保つ + // k+1 番目の要素から開始し、ヒープ長を k に保つ for (int i = k; i < nums.size(); i++) { - // 現在の要素がヒープの先頭要素より大きい場合、ヒープの先頭要素を削除し、現在の要素をヒープに入力 + // 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する if (nums[i] > heap.top()) { heap.pop(); heap.push(nums[i]); @@ -25,14 +25,14 @@ priority_queue, greater> topKHeap(vector &nums, int k return heap; } -// ドライバーコード +// Driver Code int main() { vector nums = {1, 7, 6, 3, 2}; int k = 3; priority_queue, greater> res = topKHeap(nums, k); - cout << "最大 " << k << " 個の要素は:"; + cout << "最大の " << k << " 個の要素は: "; printHeap(res); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_searching/CMakeLists.txt b/ja/codes/cpp/chapter_searching/CMakeLists.txt new file mode 100644 index 000000000..60a223d83 --- /dev/null +++ b/ja/codes/cpp/chapter_searching/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(binary_search binary_search.cpp) +add_executable(binary_search_insertion binary_search_insertion.cpp) +add_executable(binary_search_edge binary_search_edge.cpp) +add_executable(two_sum two_sum.cpp) diff --git a/ja/codes/cpp/chapter_searching/binary_search.cpp b/ja/codes/cpp/chapter_searching/binary_search.cpp index ae0761d91..54102ccbf 100644 --- a/ja/codes/cpp/chapter_searching/binary_search.cpp +++ b/ja/codes/cpp/chapter_searching/binary_search.cpp @@ -6,54 +6,54 @@ #include "../utils/common.hpp" -/* 二分探索(両端閉区間) */ +/* 二分探索(両閉区間) */ int binarySearch(vector &nums, int target) { - // 両端閉区間[0, n-1]を初期化、すなわちi、jはそれぞれ配列の最初の要素と最後の要素を指す + // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す int i = 0, j = nums.size() - 1; - // 探索区間が空になるまでループ(i > jの時空になる) + // ループし、探索区間が空になったら終了する(i > j で空) while (i <= j) { - int m = i + (j - i) / 2; // 中点インデックスmを計算 - if (nums[m] < target) // この状況はtargetが区間[m+1, j]にあることを示す + int m = i + (j - i) / 2; // 中点インデックス m を計算 + if (nums[m] < target) // この場合、target は区間 [m+1, j] にある i = m + 1; - else if (nums[m] > target) // この状況はtargetが区間[i, m-1]にあることを示す + else if (nums[m] > target) // この場合、target は区間 [i, m-1] にある j = m - 1; - else // ターゲット要素が見つかったため、そのインデックスを返す + else // 目標要素が見つかったらそのインデックスを返す return m; } - // ターゲット要素が見つからなかったため、-1を返す + // 目標要素が見つからなければ -1 を返す return -1; } /* 二分探索(左閉右開区間) */ int binarySearchLCRO(vector &nums, int target) { - // 左閉右開区間[0, n)を初期化、すなわちi、jはそれぞれ配列の最初の要素と最後の要素+1を指す + // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す int i = 0, j = nums.size(); - // 探索区間が空になるまでループ(i = jの時空になる) + // ループし、探索区間が空になったら終了する(i = j で空) while (i < j) { - int m = i + (j - i) / 2; // 中点インデックスmを計算 - if (nums[m] < target) // この状況はtargetが区間[m+1, j)にあることを示す + int m = i + (j - i) / 2; // 中点インデックス m を計算 + if (nums[m] < target) // この場合、target は区間 [m+1, j) にある i = m + 1; - else if (nums[m] > target) // この状況はtargetが区間[i, m)にあることを示す + else if (nums[m] > target) // この場合、target は区間 [i, m) にある j = m; - else // ターゲット要素が見つかったため、そのインデックスを返す + else // 目標要素が見つかったらそのインデックスを返す return m; } - // ターゲット要素が見つからなかったため、-1を返す + // 目標要素が見つからなければ -1 を返す return -1; } -/* ドライバコード */ +/* Driver Code */ int main() { int target = 6; vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; - /* 二分探索(両端閉区間) */ + /* 二分探索(両閉区間) */ int index = binarySearch(nums, target); - cout << "ターゲット要素6のインデックス =" << index << endl; + cout << "対象要素 6 のインデックス = " << index << endl; /* 二分探索(左閉右開区間) */ index = binarySearchLCRO(nums, target); - cout << "ターゲット要素6のインデックス =" << index << endl; + cout << "対象要素 6 のインデックス = " << index << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_searching/binary_search_edge.cpp b/ja/codes/cpp/chapter_searching/binary_search_edge.cpp index 9c45d53f0..ba0213efc 100644 --- a/ja/codes/cpp/chapter_searching/binary_search_edge.cpp +++ b/ja/codes/cpp/chapter_searching/binary_search_edge.cpp @@ -6,61 +6,61 @@ #include "../utils/common.hpp" -/* 挿入ポイントの二分探索(重複要素あり) */ +/* 二分探索で挿入位置を探す(重複要素あり) */ int binarySearchInsertion(const vector &nums, int target) { - int i = 0, j = nums.size() - 1; // 両端閉区間[0, n-1]を初期化 + int i = 0, j = nums.size() - 1; // 両閉区間 [0, n-1] を初期化 while (i <= j) { - int m = i + (j - i) / 2; // 中点インデックスmを計算 + int m = i + (j - i) / 2; // 中点インデックス m を計算 if (nums[m] < target) { - i = m + 1; // ターゲットは区間[m+1, j]にある + i = m + 1; // target は区間 [m+1, j] にある } else { - j = m - 1; // ターゲット未満の最初の要素は区間[i, m-1]にある + j = m - 1; // target より小さい最初の要素は区間 [i, m-1] にある } } - // 挿入ポイントiを返す + // 挿入位置 i を返す return i; } -/* 最左のターゲットの二分探索 */ +/* 最も左の target を二分探索 */ int binarySearchLeftEdge(vector &nums, int target) { - // targetの挿入ポイントを見つけることと等価 + // target の挿入位置を探すのと等価 int i = binarySearchInsertion(nums, target); - // targetが見つからなかったため、-1を返す + // target が見つからなければ、-1 を返す if (i == nums.size() || nums[i] != target) { return -1; } - // targetが見つかったため、インデックスiを返す + // target が見つかったら、インデックス i を返す return i; } -/* 最右のターゲットの二分探索 */ +/* 最も右の target を二分探索 */ int binarySearchRightEdge(vector &nums, int target) { - // 最左のtarget + 1を見つけることに変換 + // 最左の target + 1 を探す問題に変換する int i = binarySearchInsertion(nums, target + 1); - // jは最右のターゲットを指し、iはtargetより大きい最初の要素を指す + // j は最も右の target を指し、i は target より大きい最初の要素を指す int j = i - 1; - // targetが見つからなかったため、-1を返す + // target が見つからなければ、-1 を返す if (j == -1 || nums[j] != target) { return -1; } - // targetが見つかったため、インデックスjを返す + // target が見つかったら、インデックス j を返す return j; } -/* ドライバコード */ +/* Driver Code */ int main() { // 重複要素を含む配列 vector nums = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; cout << "\n配列 nums = "; printVector(nums); - // 左右の境界の二分探索 + // 二分探索で左端と右端を探す for (int target : {6, 7}) { int index = binarySearchLeftEdge(nums, target); - cout << "要素 " << target << " の最左インデックスは " << index << " です" << endl; + cout << "一番左の要素 " << target << " のインデックスは " << index << endl; index = binarySearchRightEdge(nums, target); - cout << "要素 " << target << " の最右インデックスは " << index << " です" << endl; + cout << "一番右の要素 " << target << " のインデックスは " << index << endl; } return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_searching/binary_search_insertion.cpp b/ja/codes/cpp/chapter_searching/binary_search_insertion.cpp index f003444de..f076f80c3 100644 --- a/ja/codes/cpp/chapter_searching/binary_search_insertion.cpp +++ b/ja/codes/cpp/chapter_searching/binary_search_insertion.cpp @@ -6,61 +6,61 @@ #include "../utils/common.hpp" -/* 挿入ポイントの二分探索(重複要素なし) */ +/* 二分探索で挿入位置を探す(重複要素なし) */ int binarySearchInsertionSimple(vector &nums, int target) { - int i = 0, j = nums.size() - 1; // 両端閉区間[0, n-1]を初期化 + int i = 0, j = nums.size() - 1; // 両閉区間 [0, n-1] を初期化 while (i <= j) { - int m = i + (j - i) / 2; // 中点インデックスmを計算 + int m = i + (j - i) / 2; // 中点インデックス m を計算 if (nums[m] < target) { - i = m + 1; // ターゲットは区間[m+1, j]にある + i = m + 1; // target は区間 [m+1, j] にある } else if (nums[m] > target) { - j = m - 1; // ターゲットは区間[i, m-1]にある + j = m - 1; // target は区間 [i, m-1] にある } else { - return m; // ターゲットが見つかったため、挿入ポイントmを返す + return m; // target が見つかったら、挿入位置 m を返す } } - // ターゲットが見つからなかったため、挿入ポイントiを返す + // target が見つからなければ、挿入位置 i を返す return i; } -/* 挿入ポイントの二分探索(重複要素あり) */ +/* 二分探索で挿入位置を探す(重複要素あり) */ int binarySearchInsertion(vector &nums, int target) { - int i = 0, j = nums.size() - 1; // 両端閉区間[0, n-1]を初期化 + int i = 0, j = nums.size() - 1; // 両閉区間 [0, n-1] を初期化 while (i <= j) { - int m = i + (j - i) / 2; // 中点インデックスmを計算 + int m = i + (j - i) / 2; // 中点インデックス m を計算 if (nums[m] < target) { - i = m + 1; // ターゲットは区間[m+1, j]にある + i = m + 1; // target は区間 [m+1, j] にある } else if (nums[m] > target) { - j = m - 1; // ターゲットは区間[i, m-1]にある + j = m - 1; // target は区間 [i, m-1] にある } else { - j = m - 1; // ターゲット未満の最初の要素は区間[i, m-1]にある + j = m - 1; // target より小さい最初の要素は区間 [i, m-1] にある } } - // 挿入ポイントiを返す + // 挿入位置 i を返す return i; } -/* ドライバコード */ +/* Driver Code */ int main() { // 重複要素のない配列 vector nums = {1, 3, 6, 8, 12, 15, 23, 26, 31, 35}; cout << "\n配列 nums = "; printVector(nums); - // 挿入ポイントの二分探索 + // 二分探索で挿入位置を探す for (int target : {6, 9}) { int index = binarySearchInsertionSimple(nums, target); - cout << "要素 " << target << " の挿入ポイントインデックスは " << index << " です" << endl; + cout << "要素 " << target << " の挿入位置のインデックスは " << index << endl; } // 重複要素を含む配列 nums = {1, 3, 6, 6, 6, 6, 6, 10, 12, 15}; cout << "\n配列 nums = "; printVector(nums); - // 挿入ポイントの二分探索 + // 二分探索で挿入位置を探す for (int target : {2, 6, 20}) { int index = binarySearchInsertion(nums, target); - cout << "要素 " << target << " の挿入ポイントインデックスは " << index << " です" << endl; + cout << "要素 " << target << " の挿入位置のインデックスは " << index << endl; } return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_searching/hashing_search.cpp b/ja/codes/cpp/chapter_searching/hashing_search.cpp index 6f63deb9f..b90d1d57b 100644 --- a/ja/codes/cpp/chapter_searching/hashing_search.cpp +++ b/ja/codes/cpp/chapter_searching/hashing_search.cpp @@ -8,8 +8,8 @@ /* ハッシュ探索(配列) */ int hashingSearchArray(unordered_map map, int target) { - // ハッシュテーブルのキー:ターゲット要素、値:インデックス - // ハッシュテーブルにこのキーが含まれていない場合、-1を返す + // ハッシュテーブルの key: 目標要素、value: インデックス + // ハッシュテーブルにこの key がなければ -1 を返す if (map.find(target) == map.end()) return -1; return map[target]; @@ -17,14 +17,14 @@ int hashingSearchArray(unordered_map map, int target) { /* ハッシュ探索(連結リスト) */ ListNode *hashingSearchLinkedList(unordered_map map, int target) { - // ハッシュテーブルのキー:ターゲットノード値、値:ノードオブジェクト - // キーがハッシュテーブルにない場合、nullptrを返す + // ハッシュテーブルの key: 対象ノードの値、value: ノードオブジェクト + // ハッシュテーブルにその key がなければ nullptr を返す if (map.find(target) == map.end()) return nullptr; return map[target]; } -/* ドライバコード */ +/* Driver Code */ int main() { int target = 3; @@ -33,21 +33,21 @@ int main() { // ハッシュテーブルを初期化 unordered_map map; for (int i = 0; i < nums.size(); i++) { - map[nums[i]] = i; // キー:要素、値:インデックス + map[nums[i]] = i; // key: 要素、value: インデックス } int index = hashingSearchArray(map, target); - cout << "ターゲット要素3のインデックスは " << index << " です" << endl; + cout << "対象要素 3 のインデックス = " << index << endl; /* ハッシュ探索(連結リスト) */ ListNode *head = vecToLinkedList(nums); // ハッシュテーブルを初期化 unordered_map map1; while (head != nullptr) { - map1[head->val] = head; // キー:ノード値、値:ノード + map1[head->val] = head; // key: ノード値、value: ノード head = head->next; } ListNode *node = hashingSearchLinkedList(map1, target); - cout << "ターゲットノード値3に対応するノードオブジェクトは " << node << " です" << endl; + cout << "対象ノード値 3 に対応するノードオブジェクトは " << node << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_searching/linear_search.cpp b/ja/codes/cpp/chapter_searching/linear_search.cpp index 77fd76421..eb6705451 100644 --- a/ja/codes/cpp/chapter_searching/linear_search.cpp +++ b/ja/codes/cpp/chapter_searching/linear_search.cpp @@ -10,40 +10,40 @@ int linearSearchArray(vector &nums, int target) { // 配列を走査 for (int i = 0; i < nums.size(); i++) { - // ターゲット要素が見つかったため、そのインデックスを返す + // 目標要素が見つかったらそのインデックスを返す if (nums[i] == target) return i; } - // ターゲット要素が見つからなかったため、-1を返す + // 目標要素が見つからなければ -1 を返す return -1; } /* 線形探索(連結リスト) */ ListNode *linearSearchLinkedList(ListNode *head, int target) { - // リストを走査 + // 連結リストを走査 while (head != nullptr) { - // ターゲットノードが見つかった場合、それを返す + // 対象ノードが見つかったら、それを返す if (head->val == target) return head; head = head->next; } - // ターゲットノードが見つからない場合、nullptrを返す + // 対象ノードが見つからない場合は `nullptr` を返す return nullptr; } -/* ドライバコード */ +/* Driver Code */ int main() { int target = 3; - /* 配列で線形探索を実行 */ + /* 配列で線形探索を行う */ vector nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8}; int index = linearSearchArray(nums, target); - cout << "ターゲット要素3のインデックスは " << index << " です" << endl; + cout << "対象要素 3 のインデックス = " << index << endl; - /* 連結リストで線形探索を実行 */ + /* 連結リストで線形探索を行う */ ListNode *head = vecToLinkedList(nums); ListNode *node = linearSearchLinkedList(head, target); - cout << "ターゲットノード値3に対応するノードオブジェクトは " << node << " です" << endl; + cout << "対象ノード値 3 に対応するノードオブジェクトは " << node << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_searching/two_sum.cpp b/ja/codes/cpp/chapter_searching/two_sum.cpp index 3be0d0138..0d886be55 100644 --- a/ja/codes/cpp/chapter_searching/two_sum.cpp +++ b/ja/codes/cpp/chapter_searching/two_sum.cpp @@ -6,10 +6,10 @@ #include "../utils/common.hpp" -/* 方法一:ブルートフォース列挙 */ +/* 方法 1:総当たり列挙 */ vector twoSumBruteForce(vector &nums, int target) { int size = nums.size(); - // 二重ループ、時間計算量はO(n^2) + // 2重ループのため、時間計算量は O(n^2) for (int i = 0; i < size - 1; i++) { for (int j = i + 1; j < size; j++) { if (nums[i] + nums[j] == target) @@ -19,12 +19,12 @@ vector twoSumBruteForce(vector &nums, int target) { return {}; } -/* 方法二:補助ハッシュテーブル */ +/* 方法 2:補助ハッシュテーブル */ vector twoSumHashTable(vector &nums, int target) { int size = nums.size(); - // 補助ハッシュテーブル、空間計算量はO(n) + // 補助ハッシュテーブルを使用し、空間計算量は O(n) unordered_map dic; - // 単層ループ、時間計算量はO(n) + // 単一ループで、時間計算量は O(n) for (int i = 0; i < size; i++) { if (dic.find(target - nums[i]) != dic.end()) { return {dic[target - nums[i]], i}; @@ -34,21 +34,21 @@ vector twoSumHashTable(vector &nums, int target) { return {}; } -/* ドライバコード */ +/* Driver Code */ int main() { - // ======= テストケース ======= + // ======= Test Case ======= vector nums = {2, 7, 11, 15}; int target = 13; - // ====== ドライバコード ====== - // 方法一 + // ====== Driver Code ====== + // 方法 1 vector res = twoSumBruteForce(nums, target); - cout << "方法一 res = "; + cout << "方法1 res = "; printVector(res); - // 方法二 + // 方法 2 res = twoSumHashTable(nums, target); - cout << "方法二 res = "; + cout << "方法2 res = "; printVector(res); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_sorting/CMakeLists.txt b/ja/codes/cpp/chapter_sorting/CMakeLists.txt new file mode 100644 index 000000000..e6347cf9f --- /dev/null +++ b/ja/codes/cpp/chapter_sorting/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(selection_sort selection_sort.cpp) +add_executable(bubble_sort bubble_sort.cpp) +add_executable(insertion_sort insertion_sort.cpp) +add_executable(merge_sort merge_sort.cpp) +add_executable(quick_sort quick_sort.cpp) +add_executable(heap_sort heap_sort.cpp) \ No newline at end of file diff --git a/ja/codes/cpp/chapter_sorting/bubble_sort.cpp b/ja/codes/cpp/chapter_sorting/bubble_sort.cpp index 326ecf23b..02baadf82 100644 --- a/ja/codes/cpp/chapter_sorting/bubble_sort.cpp +++ b/ja/codes/cpp/chapter_sorting/bubble_sort.cpp @@ -8,49 +8,49 @@ /* バブルソート */ void bubbleSort(vector &nums) { - // 外側ループ:未ソート範囲は[0, i] + // 外側のループ:未ソート区間は [0, i] for (int i = nums.size() - 1; i > 0; i--) { - // 内側ループ:未ソート範囲[0, i]内の最大要素を範囲の右端に交換 + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { - // nums[j]とnums[j + 1]を交換 - // ここではstdのswapを使用 + // nums[j] と nums[j + 1] を交換する + // ここでは std::swap() 関数を使用する swap(nums[j], nums[j + 1]); } } } } -/* バブルソート(フラグ最適化版)*/ +/* バブルソート(フラグ最適化) */ void bubbleSortWithFlag(vector &nums) { - // 外側ループ:未ソート範囲は[0, i] + // 外側のループ:未ソート区間は [0, i] for (int i = nums.size() - 1; i > 0; i--) { - bool flag = false; // フラグを初期化 - // 内側ループ:未ソート範囲[0, i]内の最大要素を範囲の右端に交換 + bool flag = false; // フラグを初期化する + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { - // nums[j]とnums[j + 1]を交換 - // ここではstdのswapを使用 + // nums[j] と nums[j + 1] を交換する + // ここでは std::swap() 関数を使用する swap(nums[j], nums[j + 1]); - flag = true; // 交換された要素を記録 + flag = true; // 交換する要素を記録 } } if (!flag) - break; // この回の「バブリング」で要素が交換されなかった場合、終了 + break; // このバブル処理で要素交換が一度もなければそのまま終了 } } -/* ドライバコード */ +/* Driver Code */ int main() { vector nums = {4, 1, 3, 1, 5, 2}; bubbleSort(nums); - cout << "バブルソート後、nums = "; + cout << "バブルソート完了後 nums = "; printVector(nums); vector nums1 = {4, 1, 3, 1, 5, 2}; bubbleSortWithFlag(nums1); - cout << "バブルソート後、nums1 = "; + cout << "バブルソート完了後 nums1 = "; printVector(nums1); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_sorting/bucket_sort.cpp b/ja/codes/cpp/chapter_sorting/bucket_sort.cpp index 83f941709..6a58e1357 100644 --- a/ja/codes/cpp/chapter_sorting/bucket_sort.cpp +++ b/ja/codes/cpp/chapter_sorting/bucket_sort.cpp @@ -8,22 +8,22 @@ /* バケットソート */ void bucketSort(vector &nums) { - // k = n/2個のバケットを初期化、各バケットに2つの要素を割り当てることを期待 + // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする int k = nums.size() / 2; vector> buckets(k); - // 1. 配列要素を各バケットに分配 + // 1. 配列要素を各バケットに振り分ける for (float num : nums) { - // 入力データ範囲は[0, 1)、num * kを使用してインデックス範囲[0, k-1]にマップ + // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する int i = num * k; - // bucket_idxバケットに数値を追加 + // num をバケット bucket_idx に追加 buckets[i].push_back(num); } - // 2. 各バケットをソート + // 2. 各バケットをソートする for (vector &bucket : buckets) { - // 組み込みソート関数を使用、他のソートアルゴリズムに置き換えることも可能 + // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい sort(bucket.begin(), bucket.end()); } - // 3. バケットを走査して結果をマージ + // 3. バケットを走査して結果を結合 int i = 0; for (vector &bucket : buckets) { for (float num : bucket) { @@ -32,13 +32,13 @@ void bucketSort(vector &nums) { } } -/* ドライバコード */ +/* Driver Code */ int main() { - // 入力データが浮動小数点数、範囲[0, 1)と仮定 + // 入力データは範囲 [0, 1) の浮動小数点数とする vector nums = {0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f}; bucketSort(nums); - cout << "バケットソート後、nums = "; + cout << "バケットソート完了後 nums = "; printVector(nums); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_sorting/counting_sort.cpp b/ja/codes/cpp/chapter_sorting/counting_sort.cpp index 20391b01f..bfb73c41e 100644 --- a/ja/codes/cpp/chapter_sorting/counting_sort.cpp +++ b/ja/codes/cpp/chapter_sorting/counting_sort.cpp @@ -6,21 +6,21 @@ #include "../utils/common.hpp" -/* カウントソート */ -// 簡単な実装、オブジェクトのソートには使用できない +/* 計数ソート */ +// 簡易実装のため、オブジェクトのソートには使えない void countingSortNaive(vector &nums) { - // 1. 配列の最大要素mを統計 + // 1. 配列の最大要素 m を求める int m = 0; for (int num : nums) { m = max(m, num); } - // 2. 各数字の出現回数を統計 - // counter[num]はnumの出現回数を表す + // 2. 各数値の出現回数を数える + // counter[num] は num の出現回数を表す vector counter(m + 1, 0); for (int num : nums) { counter[num]++; } - // 3. counterを走査し、各要素を元の配列numsに戻す + // 3. counter を走査し、各要素を元の配列 nums に書き戻す int i = 0; for (int num = 0; num < m + 1; num++) { for (int j = 0; j < counter[num]; j++, i++) { @@ -29,49 +29,49 @@ void countingSortNaive(vector &nums) { } } -/* カウントソート */ -// 完全な実装、オブジェクトのソートが可能で安定ソート +/* 計数ソート */ +// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである void countingSort(vector &nums) { - // 1. 配列の最大要素mを統計 + // 1. 配列の最大要素 m を求める int m = 0; for (int num : nums) { m = max(m, num); } - // 2. 各数字の出現回数を統計 - // counter[num]はnumの出現回数を表す + // 2. 各数値の出現回数を数える + // counter[num] は num の出現回数を表す vector counter(m + 1, 0); for (int num : nums) { counter[num]++; } - // 3. counterの前缀和を計算し、「出現回数」を「末尾インデックス」に変換 - // counter[num]-1はnumがresで現れる最後のインデックス + // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する + // つまり counter[num]-1 は、num が res に最後に現れるインデックス for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } - // 4. numsを逆順で走査し、各要素を結果配列resに配置 - // 結果を記録する配列resを初期化 + // 4. nums を逆順に走査し、各要素を結果配列 res に格納する + // 結果を記録するための配列 res を初期化 int n = nums.size(); vector res(n); for (int i = n - 1; i >= 0; i--) { int num = nums[i]; - res[counter[num] - 1] = num; // numを対応するインデックスに配置 - counter[num]--; // 前缀和を1減らし、numを配置する次のインデックスを取得 + res[counter[num] - 1] = num; // num を対応するインデックスに配置 + counter[num]--; // 累積和を 1 減らして、次に num を配置するインデックスを得る } - // 結果配列resで元の配列numsを上書き + // 結果配列 res で元の配列 nums を上書きする nums = res; } -/* ドライバコード */ +/* Driver Code */ int main() { vector nums = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; countingSortNaive(nums); - cout << "カウントソート(オブジェクトソート不可)後、nums = "; + cout << "カウントソート(オブジェクトはソートできない)完了後 nums = "; printVector(nums); vector nums1 = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; countingSort(nums1); - cout << "カウントソート後、nums1 = "; + cout << "カウントソート完了後 nums1 = "; printVector(nums1); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_sorting/heap_sort.cpp b/ja/codes/cpp/chapter_sorting/heap_sort.cpp index edfdb6e78..ef1546441 100644 --- a/ja/codes/cpp/chapter_sorting/heap_sort.cpp +++ b/ja/codes/cpp/chapter_sorting/heap_sort.cpp @@ -6,10 +6,10 @@ #include "../utils/common.hpp" -/* ヒープの長さはn、ノードiから上から下へヒープ化を開始 */ +/* ヒープの長さは n。ノード i から下方向にヒープ化 */ void siftDown(vector &nums, int n, int i) { while (true) { - // i、l、r の中で最大のノードを決定し、maとして記録 + // ノード i, l, r のうち値が最大のノードを ma とする int l = 2 * i + 1; int r = 2 * i + 2; int ma = i; @@ -17,38 +17,38 @@ void siftDown(vector &nums, int n, int i) { ma = l; if (r < n && nums[r] > nums[ma]) ma = r; - // ノードiが最大か、インデックスl、rが境界外の場合、それ以上のヒープ化は不要で終了 + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if (ma == i) { break; } - // 二つのノードを交換 + // 2 つのノードを交換 swap(nums[i], nums[ma]); - // 下向きにヒープ化をループ + // ループで上から下へヒープ化 i = ma; } } /* ヒープソート */ void heapSort(vector &nums) { - // ヒープ構築操作:葉以外のすべてのノードをヒープ化 + // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する for (int i = nums.size() / 2 - 1; i >= 0; --i) { siftDown(nums, nums.size(), i); } - // ヒープから最大要素を抽出し、n-1回繰り返す + // ヒープから最大要素を取り出し、n-1 回繰り返す for (int i = nums.size() - 1; i > 0; --i) { - // ルートノードを最右葉ノードと交換(最初の要素を最後の要素と交換) + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) swap(nums[0], nums[i]); - // ルートノードから上から下へヒープ化を開始 + // 根ノードを起点に、上から下へヒープ化 siftDown(nums, i, 0); } } -/* ドライバコード */ +/* Driver Code */ int main() { vector nums = {4, 1, 3, 1, 5, 2}; heapSort(nums); - cout << "ヒープソート後、nums = "; + cout << "ヒープソート完了後 nums = "; printVector(nums); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_sorting/insertion_sort.cpp b/ja/codes/cpp/chapter_sorting/insertion_sort.cpp index 031a28aac..a71df276d 100644 --- a/ja/codes/cpp/chapter_sorting/insertion_sort.cpp +++ b/ja/codes/cpp/chapter_sorting/insertion_sort.cpp @@ -8,24 +8,24 @@ /* 挿入ソート */ void insertionSort(vector &nums) { - // 外側ループ:ソート済み範囲は[0, i-1] + // 外側ループ:整列済み区間は [0, i-1] for (int i = 1; i < nums.size(); i++) { int base = nums[i], j = i - 1; - // 内側ループ:baseをソート済み範囲[0, i-1]内の正しい位置に挿入 + // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する while (j >= 0 && nums[j] > base) { - nums[j + 1] = nums[j]; // nums[j]を一つ右に移動 + nums[j + 1] = nums[j]; // nums[j] を 1 つ右へ移動する j--; } - nums[j + 1] = base; // baseを正しい位置に代入 + nums[j + 1] = base; // base を正しい位置に配置する } } -/* ドライバコード */ +/* Driver Code */ int main() { vector nums = {4, 1, 3, 1, 5, 2}; insertionSort(nums); - cout << "挿入ソート後、nums = "; + cout << "挿入ソート完了後 nums = "; printVector(nums); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_sorting/merge_sort.cpp b/ja/codes/cpp/chapter_sorting/merge_sort.cpp index 43bc1f7f0..53e0c90ad 100644 --- a/ja/codes/cpp/chapter_sorting/merge_sort.cpp +++ b/ja/codes/cpp/chapter_sorting/merge_sort.cpp @@ -6,28 +6,28 @@ #include "../utils/common.hpp" -/* 左サブ配列と右サブ配列をマージ */ +/* 左部分配列と右部分配列をマージ */ void merge(vector &nums, int left, int mid, int right) { - // 左サブ配列の区間は[left, mid]、右サブ配列の区間は[mid+1, right] - // マージ結果を保存する一時配列tmpを作成 + // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] + // マージ結果を格納する一時配列 tmp を作成 vector tmp(right - left + 1); - // 左右サブ配列の開始インデックスを初期化 + // 左右の部分配列の開始インデックスを初期化する int i = left, j = mid + 1, k = 0; - // 両サブ配列に要素がある間、小さい方の要素を一時配列にコピー + // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする while (i <= mid && j <= right) { if (nums[i] <= nums[j]) tmp[k++] = nums[i++]; else tmp[k++] = nums[j++]; } - // 左右サブ配列の残りの要素を一時配列にコピー + // 左右の部分配列の残り要素を一時配列にコピーする while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } - // 一時配列tmpの要素を元の配列numsの対応する区間にコピー + // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする for (k = 0; k < tmp.size(); k++) { nums[left + k] = tmp[k]; } @@ -37,22 +37,22 @@ void merge(vector &nums, int left, int mid, int right) { void mergeSort(vector &nums, int left, int right) { // 終了条件 if (left >= right) - return; // サブ配列の長さが1の時、再帰を終了 - // 分割段階 + return; // 部分配列の長さが 1 になったら再帰を終了 + // 分割フェーズ int mid = left + (right - left) / 2; // 中点を計算 - mergeSort(nums, left, mid); // 左サブ配列を再帰的に処理 - mergeSort(nums, mid + 1, right); // 右サブ配列を再帰的に処理 - // マージ段階 + mergeSort(nums, left, mid); // 左部分配列を再帰処理 + mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理 + // マージフェーズ merge(nums, left, mid, right); } -/* ドライバコード */ +/* Driver Code */ int main() { /* マージソート */ vector nums = {7, 3, 2, 6, 0, 1, 5, 4}; mergeSort(nums, 0, nums.size() - 1); - cout << "マージソート後、nums = "; + cout << "マージソート完了後 nums = "; printVector(nums); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_sorting/quick_sort.cpp b/ja/codes/cpp/chapter_sorting/quick_sort.cpp index 66a43a030..e4ba36bce 100644 --- a/ja/codes/cpp/chapter_sorting/quick_sort.cpp +++ b/ja/codes/cpp/chapter_sorting/quick_sort.cpp @@ -9,37 +9,30 @@ /* クイックソートクラス */ class QuickSort { private: - /* 要素を交換 */ - static void swap(vector &nums, int i, int j) { - int tmp = nums[i]; - nums[i] = nums[j]; - nums[j] = tmp; - } - - /* 分割 */ + /* 番兵分割 */ static int partition(vector &nums, int left, int right) { - // nums[left]をピボットとして使用 + // nums[left] を基準値とする int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) - j--; // 右から左へピボットより小さい最初の要素を検索 + j--; // 右から左へ基準値未満の最初の要素を探す while (i < j && nums[i] <= nums[left]) - i++; // 左から右へピボットより大きい最初の要素を検索 - swap(nums, i, j); // これら二つの要素を交換 + i++; // 左から右へ基準値より大きい最初の要素を探す + swap(nums[i], nums[j]); // この 2 つの要素を交換 } - swap(nums, i, left); // ピボットを二つのサブ配列の境界に交換 - return i; // ピボットのインデックスを返す + swap(nums[i], nums[left]); // 基準値を 2 つの部分配列の境界へ交換する + return i; // 基準値のインデックスを返す } public: /* クイックソート */ static void quickSort(vector &nums, int left, int right) { - // サブ配列の長さが1の時、再帰を終了 + // 部分配列の長さが 1 なら再帰を終了する if (left >= right) return; - // 分割 + // 番兵分割 int pivot = partition(nums, left, right); - // 左サブ配列と右サブ配列を再帰的に処理 + // 左右の部分配列を再帰処理 quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } @@ -48,119 +41,105 @@ class QuickSort { /* クイックソートクラス(中央値ピボット最適化) */ class QuickSortMedian { private: - /* 要素を交換 */ - static void swap(vector &nums, int i, int j) { - int tmp = nums[i]; - nums[i] = nums[j]; - nums[j] = tmp; - } - - /* 三つの候補要素の中央値を選択 */ + /* 3つの候補要素の中央値を選ぶ */ static int medianThree(vector &nums, int left, int mid, int right) { int l = nums[left], m = nums[mid], r = nums[right]; if ((l <= m && m <= r) || (r <= m && m <= l)) - return mid; // mはlとrの間 + return mid; // m は l と r の間 if ((m <= l && l <= r) || (r <= l && l <= m)) - return left; // lはmとrの間 + return left; // l は m と r の間 return right; } - /* 分割(三つの中央値) */ + /* 番兵による分割処理(3 点中央値) */ static int partition(vector &nums, int left, int right) { - // 三つの候補要素の中央値を選択 + // 3つの候補要素の中央値を選ぶ int med = medianThree(nums, left, (left + right) / 2, right); - // 中央値を配列の最左位置に交換 - swap(nums, left, med); - // nums[left]をピボットとして使用 + // 中央値を配列の最左端に交換する + swap(nums[left], nums[med]); + // nums[left] を基準値とする int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) - j--; // 右から左へピボットより小さい最初の要素を検索 + j--; // 右から左へ基準値未満の最初の要素を探す while (i < j && nums[i] <= nums[left]) - i++; // 左から右へピボットより大きい最初の要素を検索 - swap(nums, i, j); // これら二つの要素を交換 + i++; // 左から右へ基準値より大きい最初の要素を探す + swap(nums[i], nums[j]); // この 2 つの要素を交換 } - swap(nums, i, left); // ピボットを二つのサブ配列の境界に交換 - return i; // ピボットのインデックスを返す + swap(nums[i], nums[left]); // 基準値を 2 つの部分配列の境界へ交換する + return i; // 基準値のインデックスを返す } public: /* クイックソート */ static void quickSort(vector &nums, int left, int right) { - // サブ配列の長さが1の時、再帰を終了 + // 部分配列の長さが 1 なら再帰を終了する if (left >= right) return; - // 分割 + // 番兵分割 int pivot = partition(nums, left, right); - // 左サブ配列と右サブ配列を再帰的に処理 + // 左右の部分配列を再帰処理 quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } }; -/* クイックソートクラス(末尾再帰最適化) */ +/* クイックソートクラス(再帰深度最適化) */ class QuickSortTailCall { private: - /* 要素を交換 */ - static void swap(vector &nums, int i, int j) { - int tmp = nums[i]; - nums[i] = nums[j]; - nums[j] = tmp; - } - - /* 分割 */ + /* 番兵分割 */ static int partition(vector &nums, int left, int right) { - // nums[left]をピボットとして使用 + // nums[left] を基準値とする int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) - j--; // 右から左へピボットより小さい最初の要素を検索 + j--; // 右から左へ基準値未満の最初の要素を探す while (i < j && nums[i] <= nums[left]) - i++; // 左から右へピボットより大きい最初の要素を検索 - swap(nums, i, j); // これら二つの要素を交換 + i++; // 左から右へ基準値より大きい最初の要素を探す + swap(nums[i], nums[j]); // この 2 つの要素を交換 } - swap(nums, i, left); // ピボットを二つのサブ配列の境界に交換 - return i; // ピボットのインデックスを返す + swap(nums[i], nums[left]); // 基準値を 2 つの部分配列の境界へ交換する + return i; // 基準値のインデックスを返す } public: - /* クイックソート(末尾再帰最適化) */ + /* クイックソート(再帰深度最適化) */ static void quickSort(vector &nums, int left, int right) { - // サブ配列の長さが1の時終了 + // 部分配列の長さが 1 なら終了 while (left < right) { - // 分割操作 + // 番兵による分割処理 int pivot = partition(nums, left, right); - // 二つのサブ配列のうち短い方でクイックソートを実行 + // 2 つの部分配列のうち短いほうにクイックソートを適用する if (pivot - left < right - pivot) { - quickSort(nums, left, pivot - 1); // 左サブ配列を再帰的にソート - left = pivot + 1; // 残りの未ソート区間は[pivot + 1, right] + quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソート + left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right] } else { - quickSort(nums, pivot + 1, right); // 右サブ配列を再帰的にソート - right = pivot - 1; // 残りの未ソート区間は[left, pivot - 1] + quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソート + right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1] } } } }; -/* ドライバコード */ +/* Driver Code */ int main() { /* クイックソート */ vector nums{2, 4, 1, 0, 3, 5}; QuickSort::quickSort(nums, 0, nums.size() - 1); - cout << "クイックソート後、nums = "; + cout << "クイックソート完了後 nums = "; printVector(nums); - /* クイックソート(中央値ピボット最適化) */ + /* クイックソート(中央値の基準値で最適化) */ vector nums1 = {2, 4, 1, 0, 3, 5}; QuickSortMedian::quickSort(nums1, 0, nums1.size() - 1); - cout << "クイックソート(中央値ピボット最適化)完了、nums = "; + cout << "クイックソート(中央値ピボット最適化)完了後 nums = "; printVector(nums1); - /* クイックソート(末尾再帰最適化) */ + /* クイックソート(再帰深度最適化) */ vector nums2 = {2, 4, 1, 0, 3, 5}; QuickSortTailCall::quickSort(nums2, 0, nums2.size() - 1); - cout << "クイックソート(末尾再帰最適化)完了、nums = "; + cout << "クイックソート(再帰深度最適化)完了後 nums = "; printVector(nums2); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_sorting/radix_sort.cpp b/ja/codes/cpp/chapter_sorting/radix_sort.cpp index 1855e0024..4adebb507 100644 --- a/ja/codes/cpp/chapter_sorting/radix_sort.cpp +++ b/ja/codes/cpp/chapter_sorting/radix_sort.cpp @@ -6,60 +6,60 @@ #include "../utils/common.hpp" -/* 要素numのk番目の桁を取得、exp = 10^(k-1) */ +/* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */ int digit(int num, int exp) { - // kの代わりにexpを渡すことで、ここで繰り返される高価な冪乗計算を避けることができる + // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す return (num / exp) % 10; } -/* カウントソート(numsのk番目の桁に基づく) */ +/* 計数ソート(nums の k 桁目でソート) */ void countingSortDigit(vector &nums, int exp) { - // 10進数の桁範囲は0~9なので、長さ10のバケット配列が必要 + // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 vector counter(10, 0); int n = nums.size(); - // 数字0~9の出現回数を統計 + // 0~9 の各数字の出現回数を集計する for (int i = 0; i < n; i++) { - int d = digit(nums[i], exp); // nums[i]のk番目の桁を取得、dとして記録 - counter[d]++; // 数字dの出現回数を統計 + int d = digit(nums[i], exp); // nums[i] の第 k 位を取得し、d とする + counter[d]++; // 数字 d の出現回数を数える } - // 前缀和を計算し、「出現回数」を「配列インデックス」に変換 + // 累積和を求め、「出現回数」を「配列インデックス」に変換する for (int i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } - // 逆順で走査し、バケット統計に基づいて各要素をresに配置 + // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する vector res(n, 0); for (int i = n - 1; i >= 0; i--) { int d = digit(nums[i], exp); - int j = counter[d] - 1; // dが配列内にあるインデックスjを取得 - res[j] = nums[i]; // 現在の要素をインデックスjに配置 - counter[d]--; // dのカウントを1減らす + int j = counter[d] - 1; // d の配列内インデックス j を取得する + res[j] = nums[i]; // 現在の要素をインデックス j に格納する + counter[d]--; // d の個数を 1 減らす } - // 結果で元の配列numsを上書き + // 結果で元の配列 nums を上書きする for (int i = 0; i < n; i++) nums[i] = res[i]; } /* 基数ソート */ void radixSort(vector &nums) { - // 配列の最大要素を取得、最大桁数を判定するために使用 + // 最大桁数の判定用に配列の最大要素を取得 int m = *max_element(nums.begin(), nums.end()); - // 最下位桁から最上位桁まで走査 + // 下位桁から上位桁の順に走査する for (int exp = 1; exp <= m; exp *= 10) - // 配列要素のk番目の桁でカウントソートを実行 + // 配列要素の k 桁目に対して計数ソートを行う // k = 1 -> exp = 1 // k = 2 -> exp = 10 - // つまり、exp = 10^(k-1) + // つまり exp = 10^(k-1) countingSortDigit(nums, exp); } -/* ドライバコード */ +/* Driver Code */ int main() { // 基数ソート vector nums = {10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996}; radixSort(nums); - cout << "基数ソート後、nums = "; + cout << "基数ソート完了後 nums = "; printVector(nums); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_sorting/selection_sort.cpp b/ja/codes/cpp/chapter_sorting/selection_sort.cpp index ebdc065a1..d911eb50a 100644 --- a/ja/codes/cpp/chapter_sorting/selection_sort.cpp +++ b/ja/codes/cpp/chapter_sorting/selection_sort.cpp @@ -9,26 +9,26 @@ /* 選択ソート */ void selectionSort(vector &nums) { int n = nums.size(); - // 外側ループ:未ソート範囲は[i, n-1] + // 外側ループ:未整列区間は [i, n-1] for (int i = 0; i < n - 1; i++) { - // 内側ループ:未ソート範囲内で最小要素を見つける + // 内側のループ:未ソート区間の最小要素を見つける int k = i; for (int j = i + 1; j < n; j++) { if (nums[j] < nums[k]) k = j; // 最小要素のインデックスを記録 } - // 最小要素を未ソート範囲の最初の要素と交換 + // その最小要素を未整列区間の先頭要素と交換する swap(nums[i], nums[k]); } } -/* ドライバコード */ +/* Driver Code */ int main() { vector nums = {4, 1, 3, 1, 5, 2}; selectionSort(nums); - cout << "選択ソート後、nums = "; + cout << "選択ソート完了後 nums = "; printVector(nums); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_stack_and_queue/CMakeLists.txt b/ja/codes/cpp/chapter_stack_and_queue/CMakeLists.txt new file mode 100644 index 000000000..b55878a17 --- /dev/null +++ b/ja/codes/cpp/chapter_stack_and_queue/CMakeLists.txt @@ -0,0 +1,9 @@ +add_executable(array_deque array_deque.cpp) +add_executable(array_queue array_queue.cpp) +add_executable(array_stack array_stack.cpp) +add_executable(deque deque.cpp) +add_executable(linkedlist_deque linkedlist_deque.cpp) +add_executable(linkedlist_queue linkedlist_queue.cpp) +add_executable(linkedlist_stack linkedlist_stack.cpp) +add_executable(queue queue.cpp) +add_executable(stack stack.cpp) diff --git a/ja/codes/cpp/chapter_stack_and_queue/array_deque.cpp b/ja/codes/cpp/chapter_stack_and_queue/array_deque.cpp index 36500745a..00cf9fd1f 100644 --- a/ja/codes/cpp/chapter_stack_and_queue/array_deque.cpp +++ b/ja/codes/cpp/chapter_stack_and_queue/array_deque.cpp @@ -6,11 +6,11 @@ #include "../utils/common.hpp" -/* 循環配列に基づく両端キュークラス */ +/* 循環配列ベースの両端キュー */ class ArrayDeque { private: vector nums; // 両端キューの要素を格納する配列 - int front; // 先頭ポインタ、先頭要素を指す + int front; // 先頭ポインタ。先頭要素を指す int queSize; // 両端キューの長さ public: @@ -37,74 +37,74 @@ class ArrayDeque { /* 循環配列のインデックスを計算 */ int index(int i) { - // 剰余演算で循環配列を実現 - // iが配列の末尾を超えた場合、先頭に戻る - // iが配列の先頭を超えた場合、末尾に戻る + // 剰余演算により配列の先頭と末尾をつなげる + // i が配列の末尾を越えたら先頭に戻る + // i が配列の先頭を越えて前に出たら末尾に戻る return (i + capacity()) % capacity(); } - /* 先頭エンキュー */ + /* キュー先頭にエンキュー */ void pushFirst(int num) { if (queSize == capacity()) { - cout << "Double-ended queue is full" << endl; + cout << "両端キューがいっぱいです" << endl; return; } - // 先頭ポインタを1つ左に移動 - // 剰余演算でfrontが配列の先頭を越えて末尾に戻ることを実現 + // 先頭ポインタを左に 1 つ移動する + // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする front = index(front - 1); - // numを先頭に追加 + // num をキュー先頭に追加 nums[front] = num; queSize++; } - /* 末尾エンキュー */ + /* キュー末尾にエンキュー */ void pushLast(int num) { if (queSize == capacity()) { - cout << "Double-ended queue is full" << endl; + cout << "両端キューがいっぱいです" << endl; return; } - // 末尾ポインタを計算、末尾インデックス + 1を指す + // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す int rear = index(front + queSize); - // numを末尾に追加 + // num をキュー末尾に追加 nums[rear] = num; queSize++; } - /* 先頭デキュー */ + /* キュー先頭からデキュー */ int popFirst() { int num = peekFirst(); - // 先頭ポインタを1つ後ろに移動 + // 先頭ポインタを 1 つ後ろへ進める front = index(front + 1); queSize--; return num; } - /* 末尾デキュー */ + /* キュー末尾からデキュー */ int popLast() { int num = peekLast(); queSize--; return num; } - /* 先頭要素にアクセス */ + /* キュー先頭の要素にアクセス */ int peekFirst() { if (isEmpty()) - throw out_of_range("Double-ended queue is empty"); + throw out_of_range("両端キューが空です"); return nums[front]; } - /* 末尾要素にアクセス */ + /* キュー末尾の要素にアクセス */ int peekLast() { if (isEmpty()) - throw out_of_range("Double-ended queue is empty"); + throw out_of_range("両端キューが空です"); // 末尾要素のインデックスを計算 int last = index(front + queSize - 1); return nums[last]; } - /* 印刷用に配列を返却 */ + /* 出力用の配列を返す */ vector toVector() { - // 有効な長さ範囲内の要素のみを変換 + // 有効長の範囲内のリスト要素のみを変換 vector res(queSize); for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[index(j)]; @@ -113,44 +113,44 @@ class ArrayDeque { } }; -/* ドライバーコード */ +/* Driver Code */ int main() { /* 両端キューを初期化 */ ArrayDeque *deque = new ArrayDeque(10); deque->pushLast(3); deque->pushLast(2); deque->pushLast(5); - cout << "Double-ended queue deque = "; + cout << "両端キュー deque = "; printVector(deque->toVector()); /* 要素にアクセス */ int peekFirst = deque->peekFirst(); - cout << "Front element peekFirst = " << peekFirst << endl; + cout << "先頭要素 peekFirst = " << peekFirst << endl; int peekLast = deque->peekLast(); - cout << "Back element peekLast = " << peekLast << endl; + cout << "末尾要素 peekLast = " << peekLast << endl; - /* 要素エンキュー */ + /* 要素をエンキュー */ deque->pushLast(4); - cout << "Element 4 enqueued at the tail, deque = "; + cout << "要素 4 を末尾に追加した後 deque = "; printVector(deque->toVector()); deque->pushFirst(1); - cout << "Element 1 enqueued at the head, deque = "; + cout << "要素 1 を先頭に追加した後 deque = "; printVector(deque->toVector()); - /* 要素デキュー */ + /* 要素をデキュー */ int popLast = deque->popLast(); - cout << "Deque tail element = " << popLast << ", after dequeuing from the tail"; + cout << "末尾から取り出した要素 = " << popLast << "、末尾から取り出した後 deque = "; printVector(deque->toVector()); int popFirst = deque->popFirst(); - cout << "Deque front element = " << popFirst << ", after dequeuing from the front"; + cout << "先頭から取り出した要素 = " << popFirst << "、先頭から取り出した後 deque = "; printVector(deque->toVector()); /* 両端キューの長さを取得 */ int size = deque->size(); - cout << "Length of the double-ended queue size = " << size << endl; + cout << "両端キューの長さ size = " << size << endl; /* 両端キューが空かどうかを判定 */ bool isEmpty = deque->isEmpty(); - cout << "Is the double-ended queue empty = " << boolalpha << isEmpty << endl; + cout << "両端キューが空かどうか = " << boolalpha << isEmpty << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_stack_and_queue/array_queue.cpp b/ja/codes/cpp/chapter_stack_and_queue/array_queue.cpp index 5489b0371..0d0ba1b6b 100644 --- a/ja/codes/cpp/chapter_stack_and_queue/array_queue.cpp +++ b/ja/codes/cpp/chapter_stack_and_queue/array_queue.cpp @@ -6,11 +6,11 @@ #include "../utils/common.hpp" -/* 循環配列に基づくキュークラス */ +/* 循環配列ベースのキュー */ class ArrayQueue { private: int *nums; // キュー要素を格納する配列 - int front; // 先頭ポインタ、先頭要素を指す + int front; // 先頭ポインタ。先頭要素を指す int queSize; // キューの長さ int queCapacity; // キューの容量 @@ -44,13 +44,13 @@ class ArrayQueue { /* エンキュー */ void push(int num) { if (queSize == queCapacity) { - cout << "Queue is full" << endl; + cout << "キューがいっぱいです" << endl; return; } - // 末尾ポインタを計算、末尾インデックス + 1を指す - // 剰余演算を使用して末尾ポインタが配列の末尾から先頭に戻るようにラップ + // 末尾ポインタを計算し、末尾インデックス + 1 を指す + // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする int rear = (front + queSize) % queCapacity; - // numを末尾に追加 + // num をキュー末尾に追加 nums[rear] = num; queSize++; } @@ -58,22 +58,22 @@ class ArrayQueue { /* デキュー */ int pop() { int num = peek(); - // 先頭ポインタを1つ後ろに移動、末尾を超えた場合は配列の先頭に戻る + // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す front = (front + 1) % queCapacity; queSize--; return num; } - /* 先頭要素にアクセス */ + /* キュー先頭の要素にアクセス */ int peek() { if (isEmpty()) - throw out_of_range("Queue is empty"); + throw out_of_range("キューが空です"); return nums[front]; } - /* 配列をVectorに変換して返却 */ + /* 配列を Vector に変換して返す */ vector toVector() { - // 有効な長さ範囲内の要素のみを変換 + // 有効長の範囲内のリスト要素のみを変換 vector arr(queSize); for (int i = 0, j = front; i < queSize; i++, j++) { arr[i] = nums[j % queCapacity]; @@ -82,48 +82,48 @@ class ArrayQueue { } }; -/* ドライバーコード */ +/* Driver Code */ int main() { /* キューを初期化 */ int capacity = 10; ArrayQueue *queue = new ArrayQueue(capacity); - /* 要素エンキュー */ + /* 要素をエンキュー */ queue->push(1); queue->push(3); queue->push(2); queue->push(5); queue->push(4); - cout << "Queue queue = "; + cout << "キュー queue = "; printVector(queue->toVector()); - /* 先頭要素にアクセス */ + /* キュー先頭の要素にアクセス */ int peek = queue->peek(); - cout << "Front element peek = " << peek << endl; + cout << "先頭要素 peek = " << peek << endl; - /* 要素デキュー */ + /* 要素をデキュー */ peek = queue->pop(); - cout << "Element dequeued = " << peek << ", after dequeuing"; + cout << "取り出した要素 pop = " << peek << "、取り出し後の queue = "; printVector(queue->toVector()); /* キューの長さを取得 */ int size = queue->size(); - cout << "Length of the queue size = " << size << endl; + cout << "キューの長さ size = " << size << endl; /* キューが空かどうかを判定 */ bool empty = queue->isEmpty(); - cout << "Is the queue empty = " << empty << endl; + cout << "キューが空かどうか = " << empty << endl; - /* 循環配列をテスト */ + /* 循環配列をテストする */ for (int i = 0; i < 10; i++) { queue->push(i); queue->pop(); - cout << "After the " << i << "th round of enqueueing + dequeuing, queue = "; + cout << "第 " << i << " 回のエンキュー + デキュー後の queue = "; printVector(queue->toVector()); } - // メモリを解放 + // メモリを解放する delete queue; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_stack_and_queue/array_stack.cpp b/ja/codes/cpp/chapter_stack_and_queue/array_stack.cpp index ffde40e51..f462d4155 100644 --- a/ja/codes/cpp/chapter_stack_and_queue/array_stack.cpp +++ b/ja/codes/cpp/chapter_stack_and_queue/array_stack.cpp @@ -6,7 +6,7 @@ #include "../utils/common.hpp" -/* 配列に基づくスタッククラス */ +/* 配列ベースのスタック */ class ArrayStack { private: vector stack; @@ -34,52 +34,52 @@ class ArrayStack { return num; } - /* スタックトップ要素にアクセス */ + /* スタックトップの要素にアクセス */ int top() { if (isEmpty()) - throw out_of_range("Stack is empty"); + throw out_of_range("スタックが空です"); return stack.back(); } - /* Vectorを返却 */ + /* Vector を返す */ vector toVector() { return stack; } }; -/* ドライバーコード */ +/* Driver Code */ int main() { /* スタックを初期化 */ ArrayStack *stack = new ArrayStack(); - /* 要素プッシュ */ + /* 要素をプッシュ */ stack->push(1); stack->push(3); stack->push(2); stack->push(5); stack->push(4); - cout << "Stack stack = "; + cout << "スタック stack = "; printVector(stack->toVector()); - /* スタックトップ要素にアクセス */ + /* スタックトップの要素にアクセス */ int top = stack->top(); - cout << "Top element of the stack top = " << top << endl; + cout << "トップ要素 top = " << top << endl; - /* 要素ポップ */ + /* 要素をポップ */ top = stack->pop(); - cout << "Element popped from the stack = " << top << ", after popping"; + cout << "取り出した要素 pop = " << top << "、取り出し後の stack = "; printVector(stack->toVector()); /* スタックの長さを取得 */ int size = stack->size(); - cout << "Length of the stack size = " << size << endl; + cout << "スタックの長さ size = " << size << endl; /* 空かどうかを判定 */ bool empty = stack->isEmpty(); - cout << "Is the stack empty = " << empty << endl; + cout << "スタックが空かどうか = " << empty << endl; - // メモリを解放 + // メモリを解放する delete stack; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_stack_and_queue/deque.cpp b/ja/codes/cpp/chapter_stack_and_queue/deque.cpp index 135d025e7..5efeb85ab 100644 --- a/ja/codes/cpp/chapter_stack_and_queue/deque.cpp +++ b/ja/codes/cpp/chapter_stack_and_queue/deque.cpp @@ -6,41 +6,41 @@ #include "../utils/common.hpp" -/* ドライバーコード */ +/* Driver Code */ int main() { /* 両端キューを初期化 */ deque deque; - /* 要素エンキュー */ + /* 要素をエンキュー */ deque.push_back(2); deque.push_back(5); deque.push_back(4); deque.push_front(3); deque.push_front(1); - cout << "Double-ended queue deque = "; + cout << "両端キュー deque = "; printDeque(deque); /* 要素にアクセス */ int front = deque.front(); - cout << "Front element of the queue front = " << front << endl; + cout << "先頭要素 front = " << front << endl; int back = deque.back(); - cout << "Back element of the queue back = " << back << endl; + cout << "末尾要素 back = " << back << endl; - /* 要素デキュー */ + /* 要素をデキュー */ deque.pop_front(); - cout << "Front element dequeued = " << front << ", after dequeuing from the front"; + cout << "先頭から取り出した要素 popFront = " << front << "、先頭から取り出した後の deque = "; printDeque(deque); deque.pop_back(); - cout << "Back element dequeued = " << back << ", after dequeuing from the back"; + cout << "末尾から取り出した要素 popLast = " << back << "、末尾から取り出した後の deque = "; printDeque(deque); /* 両端キューの長さを取得 */ int size = deque.size(); - cout << "Length of the double-ended queue size = " << size << endl; + cout << "両端キューの長さ size = " << size << endl; /* 両端キューが空かどうかを判定 */ bool empty = deque.empty(); - cout << "Is the double-ended queue empty = " << empty << endl; + cout << "両端キューが空かどうか = " << empty << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp b/ja/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp index 982dabd14..8a9d62971 100644 --- a/ja/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp +++ b/ja/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp @@ -8,17 +8,17 @@ /* 双方向連結リストノード */ struct DoublyListNode { - int val; // ノードの値 - DoublyListNode *next; // 後続ノードへのポインタ - DoublyListNode *prev; // 前続ノードへのポインタ + int val; // ノード値 + DoublyListNode *next; // 後継ノードへのポインタ + DoublyListNode *prev; // 前駆ノードへのポインタ DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) { } }; -/* 双方向連結リストに基づく両端キュークラス */ +/* 双方向連結リストベースの両端キュー */ class LinkedListDeque { private: - DoublyListNode *front, *rear; // 先頭ノードfront、末尾ノードrear + DoublyListNode *front, *rear; // 先頭ノード front、末尾ノード rear int queSize = 0; // 両端キューの長さ public: @@ -26,9 +26,9 @@ class LinkedListDeque { LinkedListDeque() : front(nullptr), rear(nullptr) { } - /* デストラクタ */ + /* デストラクタメソッド */ ~LinkedListDeque() { - // 連結リストを走査、ノードを削除、メモリを解放 + // 連結リストを走査してノードを削除し、メモリを解放する DoublyListNode *pre, *cur = front; while (cur != nullptr) { pre = cur; @@ -50,31 +50,31 @@ class LinkedListDeque { /* エンキュー操作 */ void push(int num, bool isFront) { DoublyListNode *node = new DoublyListNode(num); - // リストが空の場合、frontとrearの両方をnodeに向ける + // 連結リストが空なら、front と rear の両方を node に向ける if (isEmpty()) front = rear = node; - // 先頭エンキュー操作 + // 先頭へのエンキュー操作 else if (isFront) { - // ノードをリストの先頭に追加 + // node を連結リストの先頭に追加 front->prev = node; node->next = front; - front = node; // 先頭ノードを更新 - // 末尾エンキュー操作 + front = node; // 先頭ノードを更新する + // 末尾へのエンキュー操作 } else { - // ノードをリストの末尾に追加 + // node を連結リストの末尾に追加 rear->next = node; node->prev = rear; - rear = node; // 末尾ノードを更新 + rear = node; // 末尾ノードを更新する } - queSize++; // キュー長を更新 + queSize++; // キューの長さを更新 } - /* 先頭エンキュー */ + /* キュー先頭にエンキュー */ void pushFirst(int num) { push(num, true); } - /* 末尾エンキュー */ + /* キュー末尾にエンキュー */ void pushLast(int num) { push(num, false); } @@ -82,9 +82,9 @@ class LinkedListDeque { /* デキュー操作 */ int pop(bool isFront) { if (isEmpty()) - throw out_of_range("Queue is empty"); + throw out_of_range("キューが空です"); int val; - // 先頭デキュー操作 + // キュー先頭からの取り出し if (isFront) { val = front->val; // 先頭ノードの値を一時保存 // 先頭ノードを削除 @@ -94,8 +94,8 @@ class LinkedListDeque { front->next = nullptr; } delete front; - front = fNext; // 先頭ノードを更新 - // 末尾デキュー操作 + front = fNext; // 先頭ノードを更新する + // キュー末尾からの取り出し } else { val = rear->val; // 末尾ノードの値を一時保存 // 末尾ノードを削除 @@ -105,37 +105,37 @@ class LinkedListDeque { rear->prev = nullptr; } delete rear; - rear = rPrev; // 末尾ノードを更新 + rear = rPrev; // 末尾ノードを更新する } - queSize--; // キュー長を更新 + queSize--; // キューの長さを更新 return val; } - /* 先頭デキュー */ + /* キュー先頭からデキュー */ int popFirst() { return pop(true); } - /* 末尾デキュー */ + /* キュー末尾からデキュー */ int popLast() { return pop(false); } - /* 先頭要素にアクセス */ + /* キュー先頭の要素にアクセス */ int peekFirst() { if (isEmpty()) - throw out_of_range("Double-ended queue is empty"); + throw out_of_range("両端キューが空です"); return front->val; } - /* 末尾要素にアクセス */ + /* キュー末尾の要素にアクセス */ int peekLast() { if (isEmpty()) - throw out_of_range("Double-ended queue is empty"); + throw out_of_range("両端キューが空です"); return rear->val; } - /* 印刷用に配列を返却 */ + /* 出力用の配列を返す */ vector toVector() { DoublyListNode *node = front; vector res(size()); @@ -147,48 +147,48 @@ class LinkedListDeque { } }; -/* ドライバーコード */ +/* Driver Code */ int main() { /* 両端キューを初期化 */ LinkedListDeque *deque = new LinkedListDeque(); deque->pushLast(3); deque->pushLast(2); deque->pushLast(5); - cout << "Double-ended queue deque = "; + cout << "両端キュー deque = "; printVector(deque->toVector()); /* 要素にアクセス */ int peekFirst = deque->peekFirst(); - cout << "Front element peekFirst = " << peekFirst << endl; + cout << "先頭要素 peekFirst = " << peekFirst << endl; int peekLast = deque->peekLast(); - cout << "Back element peekLast = " << peekLast << endl; + cout << "末尾要素 peekLast = " << peekLast << endl; - /* 要素エンキュー */ + /* 要素をエンキュー */ deque->pushLast(4); - cout << "Element 4 rear enqueued, deque ="; + cout << "要素 4 を末尾に追加した後の deque ="; printVector(deque->toVector()); deque->pushFirst(1); - cout << "Element 1 enqueued at the head, deque = "; + cout << "要素 1 を先頭に追加した後 deque = "; printVector(deque->toVector()); - /* 要素デキュー */ + /* 要素をデキュー */ int popLast = deque->popLast(); - cout << "Deque tail element = " << popLast << ", after dequeuing from the tail"; + cout << "末尾から取り出した要素 = " << popLast << "、末尾から取り出した後 deque = "; printVector(deque->toVector()); int popFirst = deque->popFirst(); - cout << "Deque front element = " << popFirst << ", after dequeuing from the front"; + cout << "先頭から取り出した要素 = " << popFirst << "、先頭から取り出した後 deque = "; printVector(deque->toVector()); /* 両端キューの長さを取得 */ int size = deque->size(); - cout << "Length of the double-ended queue size = " << size << endl; + cout << "両端キューの長さ size = " << size << endl; /* 両端キューが空かどうかを判定 */ bool isEmpty = deque->isEmpty(); - cout << "Is the double-ended queue empty = " << boolalpha << isEmpty << endl; + cout << "両端キューが空かどうか = " << boolalpha << isEmpty << endl; - // メモリを解放 + // メモリを解放する delete deque; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp b/ja/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp index 18cfe7371..934f167ff 100644 --- a/ja/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp +++ b/ja/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp @@ -6,10 +6,10 @@ #include "../utils/common.hpp" -/* 連結リストに基づくキュークラス */ +/* 連結リストベースのキュー */ class LinkedListQueue { private: - ListNode *front, *rear; // 先頭ノードfront、末尾ノードrear + ListNode *front, *rear; // 先頭ノード front、末尾ノード rear int queSize; public: @@ -20,7 +20,7 @@ class LinkedListQueue { } ~LinkedListQueue() { - // 連結リストを走査、ノードを削除、メモリを解放 + // 連結リストを走査してノードを削除し、メモリを解放する freeMemoryLinkedList(front); } @@ -36,14 +36,14 @@ class LinkedListQueue { /* エンキュー */ void push(int num) { - // 末尾ノードの後ろにnumを追加 + // 末尾ノードの後ろに num を追加 ListNode *node = new ListNode(num); - // キューが空の場合、先頭と末尾ノードの両方をそのノードに向ける + // キューが空なら、先頭・末尾ノードをともにそのノードに設定 if (front == nullptr) { front = node; rear = node; } - // キューが空でない場合、そのノードを末尾ノードの後ろに追加 + // キューが空でなければ、そのノードを末尾ノードの後ろに追加 else { rear->next = node; rear = node; @@ -57,20 +57,20 @@ class LinkedListQueue { // 先頭ノードを削除 ListNode *tmp = front; front = front->next; - // メモリを解放 + // メモリを解放する delete tmp; queSize--; return num; } - /* 先頭要素にアクセス */ + /* キュー先頭の要素にアクセス */ int peek() { if (size() == 0) - throw out_of_range("Queue is empty"); + throw out_of_range("キューが空です"); return front->val; } - /* 連結リストをVectorに変換して返却 */ + /* 連結リストを Vector に変換して返す */ vector toVector() { ListNode *node = front; vector res(size()); @@ -82,39 +82,39 @@ class LinkedListQueue { } }; -/* ドライバーコード */ +/* Driver Code */ int main() { /* キューを初期化 */ LinkedListQueue *queue = new LinkedListQueue(); - /* 要素エンキュー */ + /* 要素をエンキュー */ queue->push(1); queue->push(3); queue->push(2); queue->push(5); queue->push(4); - cout << "Queue queue = "; + cout << "キュー queue = "; printVector(queue->toVector()); - /* 先頭要素にアクセス */ + /* キュー先頭の要素にアクセス */ int peek = queue->peek(); - cout << "Front element peek = " << peek << endl; + cout << "先頭要素 peek = " << peek << endl; - /* 要素デキュー */ + /* 要素をデキュー */ peek = queue->pop(); - cout << "Element dequeued = " << peek << ", after dequeuing"; + cout << "取り出した要素 pop = " << peek << "、取り出し後の queue = "; printVector(queue->toVector()); /* キューの長さを取得 */ int size = queue->size(); - cout << "Length of the queue size = " << size << endl; + cout << "キューの長さ size = " << size << endl; /* キューが空かどうかを判定 */ bool empty = queue->isEmpty(); - cout << "Is the queue empty = " << empty << endl; + cout << "キューが空かどうか = " << empty << endl; - // メモリを解放 + // メモリを解放する delete queue; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp b/ja/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp index ad047849a..6b55c6eef 100644 --- a/ja/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp +++ b/ja/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp @@ -6,10 +6,10 @@ #include "../utils/common.hpp" -/* 連結リストに基づくスタッククラス */ +/* 連結リストベースのスタック */ class LinkedListStack { private: - ListNode *stackTop; // 先頭ノードをスタックトップとして使用 + ListNode *stackTop; // 先頭ノードをスタックトップとする int stkSize; // スタックの長さ public: @@ -19,7 +19,7 @@ class LinkedListStack { } ~LinkedListStack() { - // 連結リストを走査、ノードを削除、メモリを解放 + // 連結リストを走査してノードを削除し、メモリを解放する freeMemoryLinkedList(stackTop); } @@ -46,20 +46,20 @@ class LinkedListStack { int num = top(); ListNode *tmp = stackTop; stackTop = stackTop->next; - // メモリを解放 + // メモリを解放する delete tmp; stkSize--; return num; } - /* スタックトップ要素にアクセス */ + /* スタックトップの要素にアクセス */ int top() { if (isEmpty()) - throw out_of_range("Stack is empty"); + throw out_of_range("スタックが空です"); return stackTop->val; } - /* リストを配列に変換して返却 */ + /* List を Array に変換して返す */ vector toVector() { ListNode *node = stackTop; vector res(size()); @@ -71,39 +71,39 @@ class LinkedListStack { } }; -/* ドライバーコード */ +/* Driver Code */ int main() { /* スタックを初期化 */ LinkedListStack *stack = new LinkedListStack(); - /* 要素プッシュ */ + /* 要素をプッシュ */ stack->push(1); stack->push(3); stack->push(2); stack->push(5); stack->push(4); - cout << "Stack stack = "; + cout << "スタック stack = "; printVector(stack->toVector()); - /* スタックトップ要素にアクセス */ + /* スタックトップの要素にアクセス */ int top = stack->top(); - cout << "Top element of the stack top = " << top << endl; + cout << "トップ要素 top = " << top << endl; - /* 要素ポップ */ + /* 要素をポップ */ top = stack->pop(); - cout << "Element popped from the stack = " << top << ", after popping"; + cout << "取り出した要素 pop = " << top << "、取り出し後の stack = "; printVector(stack->toVector()); /* スタックの長さを取得 */ int size = stack->size(); - cout << "Length of the stack size = " << size << endl; + cout << "スタックの長さ size = " << size << endl; /* 空かどうかを判定 */ bool empty = stack->isEmpty(); - cout << "Is the stack empty = " << empty << endl; + cout << "スタックが空かどうか = " << empty << endl; - // メモリを解放 + // メモリを解放する delete stack; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_stack_and_queue/queue.cpp b/ja/codes/cpp/chapter_stack_and_queue/queue.cpp index bd97b14d8..ea62357ec 100644 --- a/ja/codes/cpp/chapter_stack_and_queue/queue.cpp +++ b/ja/codes/cpp/chapter_stack_and_queue/queue.cpp @@ -6,36 +6,36 @@ #include "../utils/common.hpp" -/* ドライバーコード */ +/* Driver Code */ int main() { /* キューを初期化 */ queue queue; - /* 要素エンキュー */ + /* 要素をエンキュー */ queue.push(1); queue.push(3); queue.push(2); queue.push(5); queue.push(4); - cout << "Queue queue = "; + cout << "キュー queue = "; printQueue(queue); - /* 先頭要素にアクセス */ + /* キュー先頭の要素にアクセス */ int front = queue.front(); - cout << "Front element of the queue front = " << front << endl; + cout << "先頭要素 front = " << front << endl; - /* 要素デキュー */ + /* 要素をデキュー */ queue.pop(); - cout << "Element dequeued = " << front << ", after dequeuing"; + cout << "取り出した要素 front = " << front << "、取り出し後の queue = "; printQueue(queue); /* キューの長さを取得 */ int size = queue.size(); - cout << "Length of the queue size = " << size << endl; + cout << "キューの長さ size = " << size << endl; /* キューが空かどうかを判定 */ bool empty = queue.empty(); - cout << "Is the queue empty = " << empty << endl; + cout << "キューが空かどうか = " << empty << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_stack_and_queue/stack.cpp b/ja/codes/cpp/chapter_stack_and_queue/stack.cpp index 9e2bceb8c..ee0a82e5c 100644 --- a/ja/codes/cpp/chapter_stack_and_queue/stack.cpp +++ b/ja/codes/cpp/chapter_stack_and_queue/stack.cpp @@ -6,36 +6,36 @@ #include "../utils/common.hpp" -/* ドライバーコード */ +/* Driver Code */ int main() { /* スタックを初期化 */ stack stack; - /* 要素プッシュ */ + /* 要素をプッシュ */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); - cout << "Stack stack = "; + cout << "スタック stack = "; printStack(stack); - /* スタックトップ要素にアクセス */ + /* スタックトップの要素にアクセス */ int top = stack.top(); - cout << "Top element of the stack top = " << top << endl; + cout << "トップ要素 top = " << top << endl; - /* 要素ポップ */ + /* 要素をポップ */ stack.pop(); // 戻り値なし - cout << "Element popped from the stack = " << top << ", after popping"; + cout << "取り出した要素 pop = " << top << "、取り出し後の stack = "; printStack(stack); /* スタックの長さを取得 */ int size = stack.size(); - cout << "Length of the stack size = " << size << endl; + cout << "スタックの長さ size = " << size << endl; /* 空かどうかを判定 */ bool empty = stack.empty(); - cout << "Is the stack empty = " << empty << endl; + cout << "スタックが空かどうか = " << empty << endl; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_tree/CMakeLists.txt b/ja/codes/cpp/chapter_tree/CMakeLists.txt new file mode 100644 index 000000000..fa7009bcb --- /dev/null +++ b/ja/codes/cpp/chapter_tree/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(avl_tree avl_tree.cpp) +add_executable(binary_search_tree binary_search_tree.cpp) +add_executable(binary_tree binary_tree.cpp) +add_executable(binary_tree_bfs binary_tree_bfs.cpp) +add_executable(binary_tree_dfs binary_tree_dfs.cpp) +add_executable(array_binary_tree array_binary_tree.cpp) \ No newline at end of file diff --git a/ja/codes/cpp/chapter_tree/array_binary_tree.cpp b/ja/codes/cpp/chapter_tree/array_binary_tree.cpp index 6e3dd2fe3..480837742 100644 --- a/ja/codes/cpp/chapter_tree/array_binary_tree.cpp +++ b/ja/codes/cpp/chapter_tree/array_binary_tree.cpp @@ -6,7 +6,7 @@ #include "../utils/common.hpp" -/* 配列ベースの二分木クラス */ +/* 配列表現による二分木クラス */ class ArrayBinaryTree { public: /* コンストラクタ */ @@ -14,30 +14,30 @@ class ArrayBinaryTree { tree = arr; } - /* リストの容量 */ + /* リスト容量 */ int size() { return tree.size(); } /* インデックス i のノードの値を取得 */ int val(int i) { - // インデックスが範囲外の場合、INT_MAX を返す(null を表す) + // インデックスが範囲外なら、空きを表す INT_MAX を返す if (i < 0 || i >= size()) return INT_MAX; return tree[i]; } - /* インデックス i のノードの左の子のインデックスを取得 */ + /* インデックス i のノードの左子ノードのインデックスを取得 */ int left(int i) { return 2 * i + 1; } - /* インデックス i のノードの右の子のインデックスを取得 */ + /* インデックス i のノードの右子ノードのインデックスを取得 */ int right(int i) { return 2 * i + 2; } - /* インデックス i のノードの親のインデックスを取得 */ + /* インデックス i のノードの親ノードのインデックスを取得 */ int parent(int i) { return (i - 1) / 2; } @@ -45,7 +45,7 @@ class ArrayBinaryTree { /* レベル順走査 */ vector levelOrder() { vector res; - // 配列を走査 + // 配列を直接走査する for (int i = 0; i < size(); i++) { if (val(i) != INT_MAX) res.push_back(val(i)); @@ -53,7 +53,7 @@ class ArrayBinaryTree { return res; } - /* 前順走査 */ + /* 先行順走査 */ vector preOrder() { vector res; dfs(0, "pre", res); @@ -77,12 +77,12 @@ class ArrayBinaryTree { private: vector tree; - /* 深さ優先走査 */ + /* 深さ優先探索 */ void dfs(int i, string order, vector &res) { - // 空の位置の場合、戻る + // 空きスロットなら返す if (val(i) == INT_MAX) return; - // 前順走査 + // 先行順走査 if (order == "pre") res.push_back(val(i)); dfs(left(i), order, res); @@ -96,42 +96,42 @@ class ArrayBinaryTree { } }; -/* ドライバーコード */ +/* Driver Code */ int main() { - // 二分木を初期化 - // INT_MAX を使用して空の位置 nullptr を表す + // 二分木を初期化する + // 空き位置 nullptr は INT_MAX で表す vector arr = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; TreeNode *root = vectorToTree(arr); cout << "\n二分木を初期化\n"; - cout << "二分木の配列表現:\n"; + cout << "二分木の配列表現:\n"; printVector(arr); - cout << "二分木の連結リスト表現:\n"; + cout << "二分木の連結リスト表現:\n"; printTree(root); - // 配列ベースの二分木クラス + // 配列表現による二分木クラス ArrayBinaryTree abt(arr); // ノードにアクセス int i = 1; int l = abt.left(i), r = abt.right(i), p = abt.parent(i); - cout << "\n現在のノードのインデックスは " << i << "、値 = " << abt.val(i) << "\n"; - cout << "その左の子のインデックスは " << l << "、値 = " << (l != INT_MAX ? to_string(abt.val(l)) : "nullptr") << "\n"; - cout << "その右の子のインデックスは " << r << "、値 = " << (r != INT_MAX ? to_string(abt.val(r)) : "nullptr") << "\n"; - cout << "その親のインデックスは " << p << "、値 = " << (p != INT_MAX ? to_string(abt.val(p)) : "nullptr") << "\n"; + cout << "\n現在のノードのインデックスは " << i << "、値は " << abt.val(i) << "\n"; + cout << "左の子ノードのインデックスは " << l << "、値は " << (abt.val(l) != INT_MAX ? to_string(abt.val(l)) : "nullptr") << "\n"; + cout << "右の子ノードのインデックスは " << r << "、値は " << (abt.val(r) != INT_MAX ? to_string(abt.val(r)) : "nullptr") << "\n"; + cout << "親ノードのインデックスは " << p << "、値は " << (abt.val(p) != INT_MAX ? to_string(abt.val(p)) : "nullptr") << "\n"; // 木を走査 vector res = abt.levelOrder(); - cout << "\nレベル順走査は:"; + cout << "\nレベル順走査: "; printVector(res); res = abt.preOrder(); - cout << "前順走査は:"; + cout << "先行順走査: "; printVector(res); res = abt.inOrder(); - cout << "中順走査は:"; + cout << "中間順走査: "; printVector(res); res = abt.postOrder(); - cout << "後順走査は:"; + cout << "後行順走査: "; printVector(res); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_tree/avl_tree.cpp b/ja/codes/cpp/chapter_tree/avl_tree.cpp index 743541db0..10f36256d 100644 --- a/ja/codes/cpp/chapter_tree/avl_tree.cpp +++ b/ja/codes/cpp/chapter_tree/avl_tree.cpp @@ -6,96 +6,96 @@ #include "../utils/common.hpp" -/* AVL木 */ +/* AVL 木 */ class AVLTree { private: - /* ノードの高さを更新 */ + /* ノードの高さを更新する */ void updateHeight(TreeNode *node) { - // ノードの高さ = 最も高い部分木の高さ + 1 + // ノードの高さは最も高い部分木の高さ + 1 に等しい node->height = max(height(node->left), height(node->right)) + 1; } - /* 右回転操作 */ + /* 右回転 */ TreeNode *rightRotate(TreeNode *node) { TreeNode *child = node->left; TreeNode *grandChild = child->right; - // childを中心にnodeを右に回転 + // child を支点として node を右回転させる child->right = node; node->left = grandChild; - // ノードの高さを更新 + // ノードの高さを更新する updateHeight(node); updateHeight(child); - // 回転後の部分木のルートを返す + // 回転後の部分木の根ノードを返す return child; } - /* 左回転操作 */ + /* 左回転 */ TreeNode *leftRotate(TreeNode *node) { TreeNode *child = node->right; TreeNode *grandChild = child->left; - // childを中心にnodeを左に回転 + // child を支点として node を左回転させる child->left = node; node->right = grandChild; - // ノードの高さを更新 + // ノードの高さを更新する updateHeight(node); updateHeight(child); - // 回転後の部分木のルートを返す + // 回転後の部分木の根ノードを返す return child; } - /* 回転操作を実行して部分木の平衡を回復 */ + /* 回転操作を行い、この部分木の平衡を回復する */ TreeNode *rotate(TreeNode *node) { - // nodeの平衡因子を取得 + // ノード node の平衡係数を取得 int _balanceFactor = balanceFactor(node); - // 左に傾いた木 + // 左に偏った木 if (_balanceFactor > 1) { if (balanceFactor(node->left) >= 0) { // 右回転 return rightRotate(node); } else { - // 先に左回転、その後右回転 + // 左回転してから右回転 node->left = leftRotate(node->left); return rightRotate(node); } } - // 右に傾いた木 + // 右に偏った木 if (_balanceFactor < -1) { if (balanceFactor(node->right) <= 0) { // 左回転 return leftRotate(node); } else { - // 先に右回転、その後左回転 + // 右回転してから左回転 node->right = rightRotate(node->right); return leftRotate(node); } } - // 平衡な木、回転不要、そのまま戻る + // 平衡木なので回転不要、そのまま返す return node; } - /* ノードを再帰的に挿入(ヘルパーメソッド) */ + /* ノードを再帰的に挿入する(補助メソッド) */ TreeNode *insertHelper(TreeNode *node, int val) { if (node == nullptr) return new TreeNode(val); - /* 1. 挿入位置を見つけてノードを挿入 */ + /* 1. 挿入位置を探索してノードを挿入 */ if (val < node->val) node->left = insertHelper(node->left, val); else if (val > node->val) node->right = insertHelper(node->right, val); else - return node; // 重複ノードは挿入しない、そのまま戻る - updateHeight(node); // ノードの高さを更新 - /* 2. 回転操作を実行して部分木の平衡を回復 */ + return node; // 重複ノードは挿入せず、そのまま返す + updateHeight(node); // ノードの高さを更新する + /* 2. 回転操作を行い、部分木の平衡を回復する */ node = rotate(node); - // 部分木のルートノードを返す + // 部分木の根ノードを返す return node; } - /* ノードを再帰的に削除(ヘルパーメソッド) */ + /* ノードを再帰的に削除する(補助メソッド) */ TreeNode *removeHelper(TreeNode *node, int val) { if (node == nullptr) return nullptr; - /* 1. ノードを見つけて削除 */ + /* 1. ノードを探索して削除 */ if (val < node->val) node->left = removeHelper(node->left, val); else if (val > node->val) @@ -103,18 +103,18 @@ class AVLTree { else { if (node->left == nullptr || node->right == nullptr) { TreeNode *child = node->left != nullptr ? node->left : node->right; - // 子ノード数 = 0、ノードを削除して戻る + // 子ノード数 = 0 の場合、node をそのまま削除して返す if (child == nullptr) { delete node; return nullptr; } - // 子ノード数 = 1、ノードを削除 + // 子ノード数 = 1 の場合、node をそのまま削除する else { delete node; node = child; } } else { - // 子ノード数 = 2、中順走査の次のノードを削除し、現在のノードと置き換える + // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える TreeNode *temp = node->right; while (temp->left != nullptr) { temp = temp->left; @@ -124,28 +124,28 @@ class AVLTree { node->val = tempVal; } } - updateHeight(node); // ノードの高さを更新 - /* 2. 回転操作を実行して部分木の平衡を回復 */ + updateHeight(node); // ノードの高さを更新する + /* 2. 回転操作を行い、部分木の平衡を回復する */ node = rotate(node); - // 部分木のルートノードを返す + // 部分木の根ノードを返す return node; } public: - TreeNode *root; // ルートノード + TreeNode *root; // 根ノード /* ノードの高さを取得 */ int height(TreeNode *node) { - // 空ノードの高さは-1、葉ノードの高さは0 + // 空ノードの高さは -1、葉ノードの高さは 0 return node == nullptr ? -1 : node->height; } - /* 平衡因子を取得 */ + /* 平衡係数を取得 */ int balanceFactor(TreeNode *node) { - // 空ノードの平衡因子は0 + // 空ノードの平衡係数は 0 if (node == nullptr) return 0; - // ノードの平衡因子 = 左部分木の高さ - 右部分木の高さ + // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ return height(node->left) - height(node->right); } @@ -159,18 +159,18 @@ class AVLTree { root = removeHelper(root, val); } - /* ノードを検索 */ + /* ノードを探索 */ TreeNode *search(int val) { TreeNode *cur = root; - // ループで検索、葉ノードを通り過ぎたら終了 + // ループで探索し、葉ノードを越えたら抜ける while (cur != nullptr) { - // 目標ノードはcurの右部分木にある + // 目標ノードは cur の右部分木にある if (cur->val < val) cur = cur->right; - // 目標ノードはcurの左部分木にある + // 目標ノードは cur の左部分木にある else if (cur->val > val) cur = cur->left; - // 目標ノードを見つけた、ループを抜ける + // 目標ノードが見つかったらループを抜ける else break; } @@ -178,11 +178,11 @@ class AVLTree { return cur; } - /*コンストラクタ*/ + /* コンストラクタ */ AVLTree() : root(nullptr) { } - /*デストラクタ*/ + /* デストラクタメソッド */ ~AVLTree() { freeMemoryTree(root); } @@ -190,23 +190,23 @@ class AVLTree { void testInsert(AVLTree &tree, int val) { tree.insert(val); - cout << "\nノード " << val << " を挿入後、AVL木は" << endl; + cout << "\nノード " << val << " を挿入した後、AVL 木は" << endl; printTree(tree.root); } void testRemove(AVLTree &tree, int val) { tree.remove(val); - cout << "\nノード " << val << " を削除後、AVL木は" << endl; + cout << "\nノード " << val << " を削除した後、AVL 木は" << endl; printTree(tree.root); } -/* ドライバーコード */ +/* Driver Code */ int main() { - /* 空のAVL木を初期化 */ + /* 空の AVL 木を初期化する */ AVLTree avlTree; /* ノードを挿入 */ - // AVL木がノード挿入後に平衡を維持する様子に注目 + // ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい testInsert(avlTree, 1); testInsert(avlTree, 2); testInsert(avlTree, 3); @@ -218,16 +218,16 @@ int main() { testInsert(avlTree, 10); testInsert(avlTree, 6); - /* 重複ノードを挿入 */ + /* 重複ノードを挿入する */ testInsert(avlTree, 7); /* ノードを削除 */ - // AVL木がノード削除後に平衡を維持する様子に注目 - testRemove(avlTree, 8); // 次数0のノードを削除 - testRemove(avlTree, 5); // 次数1のノードを削除 - testRemove(avlTree, 4); // 次数2のノードを削除 + // ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい + testRemove(avlTree, 8); // 次数 0 のノードを削除する + testRemove(avlTree, 5); // 次数 1 のノードを削除する + testRemove(avlTree, 4); // 次数 2 のノードを削除する /* ノードを検索 */ TreeNode *node = avlTree.search(7); - cout << "\n見つかったノードオブジェクトは " << node << "、ノード値 =" << node->val << endl; -} \ No newline at end of file + cout << "\n見つかったノードオブジェクトは " << node << "、ノード値 = " << node->val << endl; +} diff --git a/ja/codes/cpp/chapter_tree/binary_search_tree.cpp b/ja/codes/cpp/chapter_tree/binary_search_tree.cpp index 63e9a6750..3a8488b57 100644 --- a/ja/codes/cpp/chapter_tree/binary_search_tree.cpp +++ b/ja/codes/cpp/chapter_tree/binary_search_tree.cpp @@ -14,32 +14,32 @@ class BinarySearchTree { public: /* コンストラクタ */ BinarySearchTree() { - // 空の木を初期化 + // 空の木を初期化する root = nullptr; } - /* デストラクタ */ + /* デストラクタメソッド */ ~BinarySearchTree() { freeMemoryTree(root); } - /* 二分木のルートノードを取得 */ + /* 二分木の根ノードを取得 */ TreeNode *getRoot() { return root; } - /* ノードを検索 */ + /* ノードを探索 */ TreeNode *search(int num) { TreeNode *cur = root; - // ループで検索、葉ノードを通り過ぎたら終了 + // ループで探索し、葉ノードを越えたら抜ける while (cur != nullptr) { - // 目標ノードはcurの右部分木にある + // 目標ノードは cur の右部分木にある if (cur->val < num) cur = cur->right; - // 目標ノードはcurの左部分木にある + // 目標ノードは cur の左部分木にある else if (cur->val > num) cur = cur->left; - // 目標ノードを見つけた、ループを抜ける + // 目標ノードが見つかったらループを抜ける else break; } @@ -49,22 +49,22 @@ class BinarySearchTree { /* ノードを挿入 */ void insert(int num) { - // 木が空の場合、ルートノードを初期化 + // 木が空なら、根ノードを初期化する if (root == nullptr) { root = new TreeNode(num); return; } TreeNode *cur = root, *pre = nullptr; - // ループで検索、葉ノードを通り過ぎたら終了 + // ループで探索し、葉ノードを越えたら抜ける while (cur != nullptr) { - // 重複ノードを見つけた場合、戻る + // 重複ノードが見つかったら、直ちに返す if (cur->val == num) return; pre = cur; - // 挿入位置はcurの右部分木にある + // 挿入位置は cur の右部分木にある if (cur->val < num) cur = cur->right; - // 挿入位置はcurの左部分木にある + // 挿入位置は cur の左部分木にある else cur = cur->left; } @@ -78,93 +78,93 @@ class BinarySearchTree { /* ノードを削除 */ void remove(int num) { - // 木が空の場合、戻る + // 木が空なら、そのまま早期リターンする if (root == nullptr) return; TreeNode *cur = root, *pre = nullptr; - // ループで検索、葉ノードを通り過ぎたら終了 + // ループで探索し、葉ノードを越えたら抜ける while (cur != nullptr) { - // 削除するノードを見つけた、ループを抜ける + // 削除対象のノードが見つかったら、ループを抜ける if (cur->val == num) break; pre = cur; - // 削除するノードはcurの右部分木にある + // 削除対象ノードは cur の右部分木にある if (cur->val < num) cur = cur->right; - // 削除するノードはcurの左部分木にある + // 削除対象ノードは cur の左部分木にある else cur = cur->left; } - // 削除するノードがない場合、戻る + // 削除対象ノードがなければそのまま返す if (cur == nullptr) return; - // 子ノード数 = 0 または 1 + // 子ノード数 = 0 or 1 if (cur->left == nullptr || cur->right == nullptr) { - // 子ノード数 = 0 / 1の場合、child = nullptr / その子ノード + // 子ノード数 = 0 / 1 のとき、child = nullptr / その子ノード TreeNode *child = cur->left != nullptr ? cur->left : cur->right; - // ノードcurを削除 + // ノード cur を削除する if (cur != root) { if (pre->left == cur) pre->left = child; else pre->right = child; } else { - // 削除されるノードがルートの場合、ルートを再割り当て + // 削除ノードが根ノードなら、根ノードを再設定 root = child; } - // メモリを解放 + // メモリを解放する delete cur; } // 子ノード数 = 2 else { - // curの中順走査の次のノードを取得 + // 中順走査における cur の次ノードを取得 TreeNode *tmp = cur->right; while (tmp->left != nullptr) { tmp = tmp->left; } int tmpVal = tmp->val; - // ノードtmpを再帰的に削除 + // ノード tmp を再帰的に削除 remove(tmp->val); - // curをtmpで置き換え + // tmp で cur を上書きする cur->val = tmpVal; } } }; -/* ドライバーコード */ +/* Driver Code */ int main() { /* 二分探索木を初期化 */ BinarySearchTree *bst = new BinarySearchTree(); - // 異なる挿入順序は様々な木構造を生み出すことに注意。この特定の順序は完全二分木を作成します + // 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる vector nums = {8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}; for (int num : nums) { bst->insert(num); } - cout << endl << "初期化された二分木は\n" << endl; + cout << endl << "初期化した二分木は\n" << endl; printTree(bst->getRoot()); - /* ノードを検索 */ + /* ノードを探索 */ TreeNode *node = bst->search(7); - cout << endl << "見つかったノードオブジェクトは " << node << "、ノード値 =" << node->val << endl; + cout << endl << "見つかったノードオブジェクトは " << node << "、ノード値 = " << node->val << endl; /* ノードを挿入 */ bst->insert(16); - cout << endl << "ノード 16 を挿入後、二分木は\n" << endl; + cout << endl << "ノード 16 を挿入した後、二分木は\n" << endl; printTree(bst->getRoot()); /* ノードを削除 */ bst->remove(1); - cout << endl << "ノード 1 を削除後、二分木は\n" << endl; + cout << endl << "ノード 1 を削除した後、二分木は\n" << endl; printTree(bst->getRoot()); bst->remove(2); - cout << endl << "ノード 2 を削除後、二分木は\n" << endl; + cout << endl << "ノード 2 を削除した後、二分木は\n" << endl; printTree(bst->getRoot()); bst->remove(4); - cout << endl << "ノード 4 を削除後、二分木は\n" << endl; + cout << endl << "ノード 4 を削除した後、二分木は\n" << endl; printTree(bst->getRoot()); - // メモリを解放 + // メモリを解放する delete bst; return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_tree/binary_tree.cpp b/ja/codes/cpp/chapter_tree/binary_tree.cpp index cfdbea3de..472cb5732 100644 --- a/ja/codes/cpp/chapter_tree/binary_tree.cpp +++ b/ja/codes/cpp/chapter_tree/binary_tree.cpp @@ -6,7 +6,7 @@ #include "../utils/common.hpp" -/* ドライバーコード */ +/* Driver Code */ int main() { /* 二分木を初期化 */ // ノードを初期化 @@ -15,7 +15,7 @@ int main() { TreeNode *n3 = new TreeNode(3); TreeNode *n4 = new TreeNode(4); TreeNode *n5 = new TreeNode(5); - // ノードの参照(ポインタ)を構築 + // ノード間の参照(ポインタ)を構築する n1->left = n2; n1->right = n3; n2->left = n4; @@ -25,19 +25,19 @@ int main() { /* ノードの挿入と削除 */ TreeNode *P = new TreeNode(0); - // n1 -> n2の間にノードPを挿入 + // n1 -> n2 の間にノード P を挿入 n1->left = P; P->left = n2; - cout << endl << "ノード P を挿入後\n" << endl; + cout << endl << "ノード P を挿入した後\n" << endl; printTree(n1); - // ノードPを削除 + // ノード P を削除 n1->left = n2; - delete P; // メモリを解放 - cout << endl << "ノード P を削除後\n" << endl; + delete P; // メモリを解放する + cout << endl << "ノード P を削除した後\n" << endl; printTree(n1); - // メモリを解放 + // メモリを解放する freeMemoryTree(n1); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_tree/binary_tree_bfs.cpp b/ja/codes/cpp/chapter_tree/binary_tree_bfs.cpp index b9794aa64..a032de9d3 100644 --- a/ja/codes/cpp/chapter_tree/binary_tree_bfs.cpp +++ b/ja/codes/cpp/chapter_tree/binary_tree_bfs.cpp @@ -8,35 +8,35 @@ /* レベル順走査 */ vector levelOrder(TreeNode *root) { - // キューを初期化、ルートノードを追加 + // キューを初期化し、ルートノードを追加する queue queue; queue.push(root); - // 走査順序を保存するリストを初期化 + // 走査順序を保存するためのリストを初期化する vector vec; while (!queue.empty()) { TreeNode *node = queue.front(); - queue.pop(); // キューからデキュー - vec.push_back(node->val); // ノード値を保存 + queue.pop(); // デキュー + vec.push_back(node->val); // ノードの値を保存する if (node->left != nullptr) - queue.push(node->left); // 左の子ノードをエンキュー + queue.push(node->left); // 左子ノードをキューに追加 if (node->right != nullptr) - queue.push(node->right); // 右の子ノードをエンキュー + queue.push(node->right); // 右子ノードをキューに追加 } return vec; } -/* ドライバーコード */ +/* Driver Code */ int main() { /* 二分木を初期化 */ - // 特定の関数を使用して配列を二分木に変換 + // ここでは、配列から直接二分木を生成する関数を利用する TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); cout << endl << "二分木を初期化\n" << endl; printTree(root); /* レベル順走査 */ vector vec = levelOrder(root); - cout << endl << "レベル順走査のノード順序 = "; + cout << endl << "レベル順走査のノード出力列 = "; printVector(vec); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/chapter_tree/binary_tree_dfs.cpp b/ja/codes/cpp/chapter_tree/binary_tree_dfs.cpp index 3a2f5efc7..ad4c9b097 100644 --- a/ja/codes/cpp/chapter_tree/binary_tree_dfs.cpp +++ b/ja/codes/cpp/chapter_tree/binary_tree_dfs.cpp @@ -6,14 +6,14 @@ #include "../utils/common.hpp" -// 走査順序を保存するリストを初期化 +// 走査順序を格納するリストを初期化 vector vec; -/* 前順走査 */ +/* 先行順走査 */ void preOrder(TreeNode *root) { if (root == nullptr) return; - // 訪問優先度:ルートノード -> 左部分木 -> 右部分木 + // 訪問順序:根ノード -> 左部分木 -> 右部分木 vec.push_back(root->val); preOrder(root->left); preOrder(root->right); @@ -23,7 +23,7 @@ void preOrder(TreeNode *root) { void inOrder(TreeNode *root) { if (root == nullptr) return; - // 訪問優先度:左部分木 -> ルートノード -> 右部分木 + // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 inOrder(root->left); vec.push_back(root->val); inOrder(root->right); @@ -33,37 +33,37 @@ void inOrder(TreeNode *root) { void postOrder(TreeNode *root) { if (root == nullptr) return; - // 訪問優先度:左部分木 -> 右部分木 -> ルートノード + // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード postOrder(root->left); postOrder(root->right); vec.push_back(root->val); } -/* ドライバーコード */ +/* Driver Code */ int main() { /* 二分木を初期化 */ - // 特定の関数を使用して配列を二分木に変換 + // ここでは、配列から直接二分木を生成する関数を利用する TreeNode *root = vectorToTree(vector{1, 2, 3, 4, 5, 6, 7}); cout << endl << "二分木を初期化\n" << endl; printTree(root); - /* 前順走査 */ + /* 先行順走査 */ vec.clear(); preOrder(root); - cout << endl << "前順走査のノード順序 = "; + cout << endl << "前順走査のノード出力列 = "; printVector(vec); /* 中順走査 */ vec.clear(); inOrder(root); - cout << endl << "中順走査のノード順序 = "; + cout << endl << "中順走査のノード出力列 = "; printVector(vec); /* 後順走査 */ vec.clear(); postOrder(root); - cout << endl << "後順走査のノード順序 = "; + cout << endl << "後順走査のノード出力列 = "; printVector(vec); return 0; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/utils/CMakeLists.txt b/ja/codes/cpp/utils/CMakeLists.txt new file mode 100644 index 000000000..775a55869 --- /dev/null +++ b/ja/codes/cpp/utils/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(utils + common.hpp print_utils.hpp + list_node.hpp tree_node.hpp + vertex.hpp) \ No newline at end of file diff --git a/ja/codes/cpp/utils/common.hpp b/ja/codes/cpp/utils/common.hpp index e71d26180..c72dabd88 100644 --- a/ja/codes/cpp/utils/common.hpp +++ b/ja/codes/cpp/utils/common.hpp @@ -25,4 +25,4 @@ #include "tree_node.hpp" #include "vertex.hpp" -using namespace std; \ No newline at end of file +using namespace std; diff --git a/ja/codes/cpp/utils/list_node.hpp b/ja/codes/cpp/utils/list_node.hpp index a75d6f4a0..03fdf294f 100644 --- a/ja/codes/cpp/utils/list_node.hpp +++ b/ja/codes/cpp/utils/list_node.hpp @@ -19,7 +19,7 @@ struct ListNode { } }; -/* 配列を連結リストに逆シリアル化する */ +/* リストを連結リストにデシリアライズする */ ListNode *vecToLinkedList(vector list) { ListNode *dum = new ListNode(0); ListNode *head = dum; @@ -30,13 +30,13 @@ ListNode *vecToLinkedList(vector list) { return dum->next; } -/* 連結リストに割り当てられたメモリを解放する */ +/* 連結リストに割り当てたメモリを解放する */ void freeMemoryLinkedList(ListNode *cur) { - // メモリを解放 + // メモリを解放する ListNode *pre; while (cur != nullptr) { pre = cur; cur = cur->next; delete pre; } -} \ No newline at end of file +} diff --git a/ja/codes/cpp/utils/print_utils.hpp b/ja/codes/cpp/utils/print_utils.hpp index 3fac372fc..22582e02c 100644 --- a/ja/codes/cpp/utils/print_utils.hpp +++ b/ja/codes/cpp/utils/print_utils.hpp @@ -13,7 +13,7 @@ #include #include -/* ベクター内の要素を検索する */ +/* Find an element in a vector */ template int vecFind(const vector &vec, T ele) { int j = INT_MAX; for (int i = 0; i < vec.size(); i++) { @@ -24,7 +24,7 @@ template int vecFind(const vector &vec, T ele) { return j; } -/* ベクターを区切り文字で連結する */ +/* Concatenate a vector with a delim */ template string strJoin(const string &delim, const T &vec) { ostringstream s; for (const auto &i : vec) { @@ -36,7 +36,7 @@ template string strJoin(const string &delim, const T &vec) { return s.str(); } -/* 文字列をn回繰り返す */ +/* Repeat a string for n times */ string strRepeat(string str, int n) { ostringstream os; for (int i = 0; i < n; i++) @@ -44,7 +44,7 @@ string strRepeat(string str, int n) { return os.str(); } -/* 配列を印刷する */ +/* 配列を出力する */ template void printArray(T *arr, int n) { cout << "["; for (int i = 0; i < n - 1; i++) { @@ -56,17 +56,17 @@ template void printArray(T *arr, int n) { cout << "]" << endl; } -/* ベクター文字列オブジェクトを取得する */ +/* Get the Vector String object */ template string getVectorString(vector &list) { return "[" + strJoin(", ", list) + "]"; } -/* リストを印刷する */ +/* リストを出力する */ template void printVector(vector list) { cout << getVectorString(list) << '\n'; } -/* 行列を印刷する */ +/* 行列を出力する */ template void printVectorMatrix(vector> &matrix) { cout << "[" << '\n'; for (vector &list : matrix) @@ -74,7 +74,7 @@ template void printVectorMatrix(vector> &matrix) { cout << "]" << '\n'; } -/* 連結リストを印刷する */ +/* 連結リストを出力 */ void printLinkedList(ListNode *head) { vector list; while (head != nullptr) { @@ -104,8 +104,8 @@ void showTrunks(Trunk *p) { } /** - * 二分木を印刷する - * この木プリンターはTECHIE DELIGHTから借用しました + * 二分木を出力 + * This tree printer is borrowed from TECHIE DELIGHT * https://www.techiedelight.com/c-program-print-binary-tree/ */ void printTree(TreeNode *root, Trunk *prev, bool isRight) { @@ -139,20 +139,20 @@ void printTree(TreeNode *root, Trunk *prev, bool isRight) { printTree(root->left, &trunk, false); } -/* 二分木を印刷する */ +/* 二分木を出力 */ void printTree(TreeNode *root) { printTree(root, nullptr, false); } -/* スタックを印刷する */ +/* スタックを出力 */ template void printStack(stack stk) { - // 入力スタックを逆順にする + // Reverse the input stack stack tmp; while (!stk.empty()) { tmp.push(stk.top()); stk.pop(); } - // 印刷する文字列を生成 + // Generate the string to print ostringstream s; bool flag = true; while (!tmp.empty()) { @@ -166,9 +166,9 @@ template void printStack(stack stk) { cout << "[" + s.str() + "]" << '\n'; } -/* キューを印刷する */ +/* キューを出力する */ template void printQueue(queue queue) { - // 印刷する文字列を生成 + // Generate the string to print ostringstream s; bool flag = true; while (!queue.empty()) { @@ -182,9 +182,9 @@ template void printQueue(queue queue) { cout << "[" + s.str() + "]" << '\n'; } -/* デックを印刷する */ +/* 両端キューを出力する */ template void printDeque(deque deque) { - // 印刷する文字列を生成 + // Generate the string to print ostringstream s; bool flag = true; while (!deque.empty()) { @@ -198,15 +198,15 @@ template void printDeque(deque deque) { cout << "[" + s.str() + "]" << '\n'; } -/* ハッシュテーブルを印刷する */ -// キー値ペアの型を指定するためにテンプレートパラメータTKeyとTValueを定義 +/* ハッシュテーブルを出力 */ +// キーと値の型を指定するためのテンプレート引数 TKey と TValue を定義 template void printHashMap(unordered_map map) { for (auto kv : map) { cout << kv.first << " -> " << kv.second << '\n'; } } -/* priority_queueコンテナの基礎となるストレージを公開する */ +/* Expose the underlying storage of the priority_queue container */ template S &Container(priority_queue &pq) { struct HackedQueue : private priority_queue { static S &Container(priority_queue &pq) { @@ -216,13 +216,13 @@ template S &Container(priority_queue void printHeap(priority_queue &heap) { vector vec = Container(heap); - cout << "ヒープの配列表現:"; + cout << "ヒープの配列表現:"; printVector(vec); - cout << "ヒープの木表現:" << endl; + cout << "ヒープの木構造表現:" << endl; TreeNode *root = vectorToTree(vec); printTree(root); freeMemoryTree(root); -} \ No newline at end of file +} diff --git a/ja/codes/cpp/utils/tree_node.hpp b/ja/codes/cpp/utils/tree_node.hpp index 425920cc4..50e61d6c9 100644 --- a/ja/codes/cpp/utils/tree_node.hpp +++ b/ja/codes/cpp/utils/tree_node.hpp @@ -11,7 +11,7 @@ using namespace std; -/* 二分木ノード構造 */ +/* 二分木ノード構造体 */ struct TreeNode { int val{}; int height = 0; @@ -23,23 +23,23 @@ struct TreeNode { } }; -// シリアル化エンコーディング規則については以下を参照: +// シリアライズの符号化規則は以下を参照: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ -// 二分木の配列表現: +// 二分木の配列表現: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] -// 二分木の連結リスト表現: -// /——— 15 -// /——— 7 -// /——— 3 -// | \——— 6 -// | \——— 12 +// 二分木の連結リスト表現: +// /——— 15 +// /——— 7 +// /——— 3 +// | \——— 6 +// | \——— 12 // ——— 1 -// \——— 2 -// | /——— 9 -// \——— 4 -// \——— 8 +// \——— 2 +// | /——— 9 +// \——— 4 +// \——— 8 -/* 配列を二分木に逆シリアル化する:再帰的 */ +/* リストを二分木にデシリアライズする: 再帰 */ TreeNode *vectorToTreeDFS(vector &arr, int i) { if (i < 0 || i >= arr.size() || arr[i] == INT_MAX) { return nullptr; @@ -50,12 +50,12 @@ TreeNode *vectorToTreeDFS(vector &arr, int i) { return root; } -/* 配列を二分木に逆シリアル化する */ +/* リストを二分木にデシリアライズする */ TreeNode *vectorToTree(vector arr) { return vectorToTreeDFS(arr, 0); } -/* 二分木を配列にシリアル化する:再帰的 */ +/* 二分木をリストにシリアライズする: 再帰 */ void treeToVecorDFS(TreeNode *root, int i, vector &res) { if (root == nullptr) return; @@ -67,18 +67,18 @@ void treeToVecorDFS(TreeNode *root, int i, vector &res) { treeToVecorDFS(root->right, 2 * i + 2, res); } -/* 二分木を配列にシリアル化する */ +/* 二分木をリストにシリアライズする */ vector treeToVecor(TreeNode *root) { vector res; treeToVecorDFS(root, 0, res); return res; } -/* 二分木に割り当てられたメモリを解放する */ +/* 二分木のメモリを解放する */ void freeMemoryTree(TreeNode *root) { if (root == nullptr) return; freeMemoryTree(root->left); freeMemoryTree(root->right); delete root; -} \ No newline at end of file +} diff --git a/ja/codes/cpp/utils/vertex.hpp b/ja/codes/cpp/utils/vertex.hpp index 34b1d519a..a64243aa8 100644 --- a/ja/codes/cpp/utils/vertex.hpp +++ b/ja/codes/cpp/utils/vertex.hpp @@ -17,7 +17,7 @@ struct Vertex { } }; -/* 値のリストvals を入力し、頂点のリストvets を返す */ +/* 値リスト vals を入力し、頂点リスト vets を返す */ vector valsToVets(vector vals) { vector vets; for (int val : vals) { @@ -26,11 +26,11 @@ vector valsToVets(vector vals) { return vets; } -/* 頂点のリストvets を入力し、値のリストvals を返す */ +/* 頂点リスト vets を入力し、値リスト vals を返す */ vector vetsToVals(vector vets) { vector vals; for (Vertex *vet : vets) { vals.push_back(vet->val); } return vals; -} \ No newline at end of file +} diff --git a/ja/codes/csharp/.editorconfig b/ja/codes/csharp/.editorconfig new file mode 100644 index 000000000..0a2c1df58 --- /dev/null +++ b/ja/codes/csharp/.editorconfig @@ -0,0 +1,88 @@ +# CSharp formatting rules +[*.cs] +csharp_new_line_before_open_brace = none +csharp_new_line_before_else = false +csharp_new_line_before_catch = false +csharp_new_line_before_finally = false +csharp_indent_labels = one_less_than_current +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent + +# CS8981: The type name only contains lower-cased ascii characters. Such names may become reserved for the language. +dotnet_diagnostic.CS8981.severity = silent + +# IDE1006: Naming Styles +dotnet_diagnostic.IDE1006.severity = silent + +# CA1822: Mark members as static +dotnet_diagnostic.CA1822.severity = silent + +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf + +# IDE0040: Add accessibility modifiers +dotnet_diagnostic.IDE0040.severity = silent + +# IDE0044: Add readonly modifier +dotnet_diagnostic.IDE0044.severity = silent diff --git a/ja/codes/csharp/.gitignore b/ja/codes/csharp/.gitignore new file mode 100644 index 000000000..a4b66a94a --- /dev/null +++ b/ja/codes/csharp/.gitignore @@ -0,0 +1,5 @@ +.idea/ +.vs/ +obj/ +.Debug +bin/ diff --git a/ja/codes/csharp/GlobalUsing.cs b/ja/codes/csharp/GlobalUsing.cs new file mode 100644 index 000000000..402066ff4 --- /dev/null +++ b/ja/codes/csharp/GlobalUsing.cs @@ -0,0 +1,3 @@ +global using NUnit.Framework; +global using hello_algo.utils; +global using System.Text; \ No newline at end of file diff --git a/ja/codes/csharp/chapter_array_and_linkedlist/array.cs b/ja/codes/csharp/chapter_array_and_linkedlist/array.cs new file mode 100644 index 000000000..37ccf9594 --- /dev/null +++ b/ja/codes/csharp/chapter_array_and_linkedlist/array.cs @@ -0,0 +1,107 @@ +// File: array.cs +// Created Time: 2022-12-14 +// Author: mingXta (1195669834@qq.com) + +namespace hello_algo.chapter_array_and_linkedlist; + +public class array { + /* 要素へランダムアクセス */ + int RandomAccess(int[] nums) { + Random random = new(); + // 区間 [0, nums.Length) からランダムに数字を 1 つ選ぶ + int randomIndex = random.Next(nums.Length); + // ランダムな要素を取得して返す + int randomNum = nums[randomIndex]; + return randomNum; + } + + /* 配列長を拡張する */ + int[] Extend(int[] nums, int enlarge) { + // 拡張後の長さを持つ配列を初期化する + int[] res = new int[nums.Length + enlarge]; + // 元の配列の全要素を新しい配列にコピー + for (int i = 0; i < nums.Length; i++) { + res[i] = nums[i]; + } + // 拡張後の新しい配列を返す + return res; + } + + /* 配列の index 番目に要素 num を挿入 */ + void Insert(int[] nums, int num, int index) { + // インデックス index 以降の全要素を 1 つ後ろへ移動する + for (int i = nums.Length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // index の要素に num を代入する + nums[index] = num; + } + + /* index の要素を削除する */ + void Remove(int[] nums, int index) { + // インデックス index より後ろの全要素を 1 つ前へ移動する + for (int i = index; i < nums.Length - 1; i++) { + nums[i] = nums[i + 1]; + } + } + + /* 配列を走査 */ + void Traverse(int[] nums) { + int count = 0; + // インデックスで配列を走査 + for (int i = 0; i < nums.Length; i++) { + count += nums[i]; + } + // 配列要素を直接走査 + foreach (int num in nums) { + count += num; + } + } + + /* 配列内で指定要素を探す */ + int Find(int[] nums, int target) { + for (int i = 0; i < nums.Length; i++) { + if (nums[i] == target) + return i; + } + return -1; + } + + /* 補助関数:配列を文字列に変換 */ + string ToString(int[] nums) { + return string.Join(",", nums); + } + + + [Test] + public void Test() { + // 配列を初期化 + int[] arr = new int[5]; + Console.WriteLine("配列 arr = " + ToString(arr)); + int[] nums = [1, 3, 2, 5, 4]; + Console.WriteLine("配列 nums = " + ToString(nums)); + + // ランダムアクセス + int randomNum = RandomAccess(nums); + Console.WriteLine("nums からランダムな要素を取得 " + randomNum); + + // 長さを拡張 + nums = Extend(nums, 3); + Console.WriteLine("配列の長さを 8 まで拡張すると nums = " + ToString(nums)); + + // 要素を挿入する + Insert(nums, 6, 3); + Console.WriteLine("インデックス 3 に数値 6 を挿入すると nums = " + ToString(nums)); + + // 要素を削除 + Remove(nums, 2); + Console.WriteLine("インデックス 2 の要素を削除すると nums = " + ToString(nums)); + + // 配列を走査 + Traverse(nums); + + // 要素を探索する + int index = Find(nums, 3); + Console.WriteLine("nums 内で要素 3 を検索するとインデックス = " + index); + } +} diff --git a/ja/codes/csharp/chapter_array_and_linkedlist/linked_list.cs b/ja/codes/csharp/chapter_array_and_linkedlist/linked_list.cs new file mode 100644 index 000000000..7ddd67253 --- /dev/null +++ b/ja/codes/csharp/chapter_array_and_linkedlist/linked_list.cs @@ -0,0 +1,80 @@ +// File: linked_list.cs +// Created Time: 2022-12-16 +// Author: mingXta (1195669834@qq.com) + +namespace hello_algo.chapter_array_and_linkedlist; + +public class linked_list { + /* 連結リストでノード n0 の後ろにノード P を挿入する */ + void Insert(ListNode n0, ListNode P) { + ListNode? n1 = n0.next; + P.next = n1; + n0.next = P; + } + + /* 連結リストでノード n0 の直後のノードを削除する */ + void Remove(ListNode n0) { + if (n0.next == null) + return; + // n0 -> P -> n1 + ListNode P = n0.next; + ListNode? n1 = P.next; + n0.next = n1; + } + + /* 連結リスト内で index 番目のノードにアクセス */ + ListNode? Access(ListNode? head, int index) { + for (int i = 0; i < index; i++) { + if (head == null) + return null; + head = head.next; + } + return head; + } + + /* 連結リストで値が target の最初のノードを探す */ + int Find(ListNode? head, int target) { + int index = 0; + while (head != null) { + if (head.val == target) + return index; + head = head.next; + index++; + } + return -1; + } + + + [Test] + public void Test() { + // 連結リストを初期化する + // 各ノードを初期化する + ListNode n0 = new(1); + ListNode n1 = new(3); + ListNode n2 = new(2); + ListNode n3 = new(5); + ListNode n4 = new(4); + // ノード間の参照を構築する + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + Console.WriteLine($"初期化した連結リストは{n0}"); + + // ノードを挿入 + Insert(n0, new ListNode(0)); + Console.WriteLine($"ノード挿入後の連結リストは{n0}"); + + // ノードを削除 + Remove(n0); + Console.WriteLine($"ノード削除後の連結リストは{n0}"); + + // ノードにアクセス + ListNode? node = Access(n0, 3); + Console.WriteLine($"連結リストのインデックス 3 にあるノードの値 = {node?.val}"); + + // ノードを探索 + int index = Find(n0, 2); + Console.WriteLine($"連結リスト内で値が 2 のノードのインデックス = {index}"); + } +} diff --git a/ja/codes/csharp/chapter_array_and_linkedlist/list.cs b/ja/codes/csharp/chapter_array_and_linkedlist/list.cs new file mode 100644 index 000000000..645100b0a --- /dev/null +++ b/ja/codes/csharp/chapter_array_and_linkedlist/list.cs @@ -0,0 +1,66 @@ +/** + * File: list.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_array_and_linkedlist; + +public class list { + [Test] + public void Test() { + + /* リストを初期化 */ + int[] numbers = [1, 3, 2, 5, 4]; + List nums = [.. numbers]; + Console.WriteLine("リスト nums = " + string.Join(",", nums)); + + /* 要素にアクセス */ + int num = nums[1]; + Console.WriteLine("インデックス 1 の要素にアクセスすると num = " + num); + + /* 要素を更新 */ + nums[1] = 0; + Console.WriteLine("インデックス 1 の要素を 0 に更新すると nums = " + string.Join(",", nums)); + + /* リストを空にする */ + nums.Clear(); + Console.WriteLine("リストを空にした後 nums = " + string.Join(",", nums)); + + /* 末尾に要素を追加 */ + nums.Add(1); + nums.Add(3); + nums.Add(2); + nums.Add(5); + nums.Add(4); + Console.WriteLine("要素を追加した後 nums = " + string.Join(",", nums)); + + /* 中間に要素を挿入 */ + nums.Insert(3, 6); + Console.WriteLine("インデックス 3 に数値 6 を挿入すると nums = " + string.Join(",", nums)); + + /* 要素を削除 */ + nums.RemoveAt(3); + Console.WriteLine("インデックス 3 の要素を削除すると nums = " + string.Join(",", nums)); + + /* インデックスでリストを走査 */ + int count = 0; + for (int i = 0; i < nums.Count; i++) { + count += nums[i]; + } + /* リスト要素を直接走査 */ + count = 0; + foreach (int x in nums) { + count += x; + } + + /* 2 つのリストを連結する */ + List nums1 = [6, 8, 7, 10, 9]; + nums.AddRange(nums1); + Console.WriteLine("リスト nums1 を nums の後ろに連結すると nums = " + string.Join(",", nums)); + + /* リストをソート */ + nums.Sort(); // ソート後、リスト要素は小さい順に並ぶ + Console.WriteLine("リストをソートした後 nums = " + string.Join(",", nums)); + } +} diff --git a/ja/codes/csharp/chapter_array_and_linkedlist/my_list.cs b/ja/codes/csharp/chapter_array_and_linkedlist/my_list.cs new file mode 100644 index 000000000..024303a89 --- /dev/null +++ b/ja/codes/csharp/chapter_array_and_linkedlist/my_list.cs @@ -0,0 +1,144 @@ +/** + * File: my_list.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_array_and_linkedlist; + +/* リストクラス */ +class MyList { + private int[] arr; // 配列(リスト要素を格納) + private int arrCapacity = 10; // リスト容量 + private int arrSize = 0; // リストの長さ(現在の要素数) + private readonly int extendRatio = 2; // リスト拡張時の増加倍率 + + /* コンストラクタ */ + public MyList() { + arr = new int[arrCapacity]; + } + + /* リストの長さを取得(現在の要素数) */ + public int Size() { + return arrSize; + } + + /* リスト容量を取得する */ + public int Capacity() { + return arrCapacity; + } + + /* 要素にアクセス */ + public int Get(int index) { + // インデックスが範囲外なら例外を送出する。以下同様 + if (index < 0 || index >= arrSize) + throw new IndexOutOfRangeException("インデックスが範囲外です"); + return arr[index]; + } + + /* 要素を更新 */ + public void Set(int index, int num) { + if (index < 0 || index >= arrSize) + throw new IndexOutOfRangeException("インデックスが範囲外です"); + arr[index] = num; + } + + /* 末尾に要素を追加 */ + public void Add(int num) { + // 要素数が容量を超えると、拡張機構が発動する + if (arrSize == arrCapacity) + ExtendCapacity(); + arr[arrSize] = num; + // 要素数を更新 + arrSize++; + } + + /* 中間に要素を挿入 */ + public void Insert(int index, int num) { + if (index < 0 || index >= arrSize) + throw new IndexOutOfRangeException("インデックスが範囲外です"); + // 要素数が容量を超えると、拡張機構が発動する + if (arrSize == arrCapacity) + ExtendCapacity(); + // index 以降の要素をすべて 1 つ後ろへずらす + for (int j = arrSize - 1; j >= index; j--) { + arr[j + 1] = arr[j]; + } + arr[index] = num; + // 要素数を更新 + arrSize++; + } + + /* 要素を削除 */ + public int Remove(int index) { + if (index < 0 || index >= arrSize) + throw new IndexOutOfRangeException("インデックスが範囲外です"); + int num = arr[index]; + // インデックス index より後の要素をすべて 1 つ前に移動する + for (int j = index; j < arrSize - 1; j++) { + arr[j] = arr[j + 1]; + } + // 要素数を更新 + arrSize--; + // 削除された要素を返す + return num; + } + + /* リストの拡張 */ + public void ExtendCapacity() { + // `arrCapacity * extendRatio` の長さを持つ配列を新規作成し、元の配列を新しい配列にコピーする + Array.Resize(ref arr, arrCapacity * extendRatio); + // リストの容量を更新 + arrCapacity = arr.Length; + } + + /* リストを配列に変換する */ + public int[] ToArray() { + // 有効長の範囲内のリスト要素のみを変換 + int[] arr = new int[arrSize]; + for (int i = 0; i < arrSize; i++) { + arr[i] = Get(i); + } + return arr; + } +} + +public class my_list { + [Test] + public void Test() { + /* リストを初期化 */ + MyList nums = new(); + /* 末尾に要素を追加 */ + nums.Add(1); + nums.Add(3); + nums.Add(2); + nums.Add(5); + nums.Add(4); + Console.WriteLine("リスト nums = " + string.Join(",", nums.ToArray()) + + " ,容量 = " + nums.Capacity() + " ,長さ = " + nums.Size()); + + /* 中間に要素を挿入 */ + nums.Insert(3, 6); + Console.WriteLine("インデックス 3 に数値 6 を挿入すると nums = " + string.Join(",", nums.ToArray())); + + /* 要素を削除 */ + nums.Remove(3); + Console.WriteLine("インデックス 3 の要素を削除すると nums = " + string.Join(",", nums.ToArray())); + + /* 要素にアクセス */ + int num = nums.Get(1); + Console.WriteLine("インデックス 1 の要素にアクセスすると num = " + num); + + /* 要素を更新 */ + nums.Set(1, 0); + Console.WriteLine("インデックス 1 の要素を 0 に更新すると nums = " + string.Join(",", nums.ToArray())); + + /* 拡張機構をテストする */ + for (int i = 0; i < 10; i++) { + // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する + nums.Add(i); + } + Console.WriteLine("拡張後のリスト nums = " + string.Join(",", nums.ToArray()) + + " ,容量 = " + nums.Capacity() + " ,長さ = " + nums.Size()); + } +} diff --git a/ja/codes/csharp/chapter_backtracking/n_queens.cs b/ja/codes/csharp/chapter_backtracking/n_queens.cs new file mode 100644 index 000000000..3e7079651 --- /dev/null +++ b/ja/codes/csharp/chapter_backtracking/n_queens.cs @@ -0,0 +1,76 @@ +/** + * File: n_queens.cs + * Created Time: 2023-05-04 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class n_queens { + /* バックトラッキング:N クイーン */ + void Backtrack(int row, int n, List> state, List>> res, + bool[] cols, bool[] diags1, bool[] diags2) { + // すべての行への配置が完了したら、解を記録する + if (row == n) { + List> copyState = []; + foreach (List sRow in state) { + copyState.Add(new List(sRow)); + } + res.Add(copyState); + return; + } + // すべての列を走査 + for (int col = 0; col < n; col++) { + // このマスに対応する主対角線と副対角線を計算 + int diag1 = row - col + n - 1; + int 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; + } + } + } + + /* N クイーンを解く */ + List>> NQueens(int n) { + // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す + List> state = []; + for (int i = 0; i < n; i++) { + List row = []; + for (int j = 0; j < n; j++) { + row.Add("#"); + } + state.Add(row); + } + bool[] cols = new bool[n]; // 列にクイーンがあるか記録 + bool[] diags1 = new bool[2 * n - 1]; // 主対角線にクイーンがあるかを記録 + bool[] diags2 = new bool[2 * n - 1]; // 副対角線にクイーンがあるかを記録 + List>> res = []; + + Backtrack(0, n, state, res, cols, diags1, diags2); + + return res; + } + + [Test] + public void Test() { + int n = 4; + List>> res = NQueens(n); + + Console.WriteLine("盤面の縦横サイズの入力値は " + n); + Console.WriteLine("クイーンの配置パターンは全部で " + res.Count + " 通り"); + foreach (List> state in res) { + Console.WriteLine("--------------------"); + foreach (List row in state) { + PrintUtil.PrintList(row); + } + } + } +} diff --git a/ja/codes/csharp/chapter_backtracking/permutations_i.cs b/ja/codes/csharp/chapter_backtracking/permutations_i.cs new file mode 100644 index 000000000..6a0148c7d --- /dev/null +++ b/ja/codes/csharp/chapter_backtracking/permutations_i.cs @@ -0,0 +1,53 @@ +/** + * File: permutations_i.cs + * Created Time: 2023-04-24 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class permutations_i { + /* バックトラッキング:順列 I */ + void Backtrack(List state, int[] choices, bool[] selected, List> res) { + // 状態の長さが要素数に等しければ、解を記録 + if (state.Count == choices.Length) { + res.Add(new List(state)); + return; + } + // すべての選択肢を走査 + for (int i = 0; i < choices.Length; i++) { + int choice = choices[i]; + // 枝刈り:要素の重複選択を許可しない + if (!selected[i]) { + // 試行: 選択を行い、状態を更新 + selected[i] = true; + state.Add(choice); + // 次の選択へ進む + Backtrack(state, choices, selected, res); + // バックトラック:選択を取り消し、前の状態に戻す + selected[i] = false; + state.RemoveAt(state.Count - 1); + } + } + } + + /* 全順列 I */ + List> PermutationsI(int[] nums) { + List> res = []; + Backtrack([], nums, new bool[nums.Length], res); + return res; + } + + [Test] + public void Test() { + int[] nums = [1, 2, 3]; + + List> res = PermutationsI(nums); + + Console.WriteLine("入力配列 nums = " + string.Join(", ", nums)); + Console.WriteLine("すべての順列 res = "); + foreach (List permutation in res) { + PrintUtil.PrintList(permutation); + } + } +} diff --git a/ja/codes/csharp/chapter_backtracking/permutations_ii.cs b/ja/codes/csharp/chapter_backtracking/permutations_ii.cs new file mode 100644 index 000000000..8102582b6 --- /dev/null +++ b/ja/codes/csharp/chapter_backtracking/permutations_ii.cs @@ -0,0 +1,55 @@ +/** + * File: permutations_ii.cs + * Created Time: 2023-04-24 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class permutations_ii { + /* バックトラッキング:順列 II */ + void Backtrack(List state, int[] choices, bool[] selected, List> res) { + // 状態の長さが要素数に等しければ、解を記録 + if (state.Count == choices.Length) { + res.Add(new List(state)); + return; + } + // すべての選択肢を走査 + HashSet duplicated = []; + for (int i = 0; i < choices.Length; i++) { + int choice = choices[i]; + // 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない + if (!selected[i] && !duplicated.Contains(choice)) { + // 試行: 選択を行い、状態を更新 + duplicated.Add(choice); // 選択済みの要素値を記録 + selected[i] = true; + state.Add(choice); + // 次の選択へ進む + Backtrack(state, choices, selected, res); + // バックトラック:選択を取り消し、前の状態に戻す + selected[i] = false; + state.RemoveAt(state.Count - 1); + } + } + } + + /* 全順列 II */ + List> PermutationsII(int[] nums) { + List> res = []; + Backtrack([], nums, new bool[nums.Length], res); + return res; + } + + [Test] + public void Test() { + int[] nums = [1, 2, 2]; + + List> res = PermutationsII(nums); + + Console.WriteLine("入力配列 nums = " + string.Join(", ", nums)); + Console.WriteLine("すべての順列 res = "); + foreach (List permutation in res) { + PrintUtil.PrintList(permutation); + } + } +} diff --git a/ja/codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs b/ja/codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs new file mode 100644 index 000000000..75460140f --- /dev/null +++ b/ja/codes/csharp/chapter_backtracking/preorder_traversal_i_compact.cs @@ -0,0 +1,37 @@ +/** + * File: preorder_traversal_i_compact.cs + * Created Time: 2023-04-17 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class preorder_traversal_i_compact { + List res = []; + + /* 前順走査:例題 1 */ + void PreOrder(TreeNode? root) { + if (root == null) { + return; + } + if (root.val == 7) { + // 解を記録 + res.Add(root); + } + PreOrder(root.left); + PreOrder(root.right); + } + + [Test] + public void Test() { + TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); + Console.WriteLine("\n二分木を初期化"); + PrintUtil.PrintTree(root); + + // 先行順走査 + PreOrder(root); + + Console.WriteLine("\n値が 7 のノードをすべて出力"); + PrintUtil.PrintList(res.Select(p => p.val).ToList()); + } +} diff --git a/ja/codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs b/ja/codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs new file mode 100644 index 000000000..d72ac79a0 --- /dev/null +++ b/ja/codes/csharp/chapter_backtracking/preorder_traversal_ii_compact.cs @@ -0,0 +1,44 @@ +/** + * File: preorder_traversal_ii_compact.cs + * Created Time: 2023-04-17 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class preorder_traversal_ii_compact { + List path = []; + List> res = []; + + /* 前順走査:例題 2 */ + void PreOrder(TreeNode? root) { + if (root == null) { + return; + } + // 試す + path.Add(root); + if (root.val == 7) { + // 解を記録 + res.Add(new List(path)); + } + PreOrder(root.left); + PreOrder(root.right); + // バックトラック + path.RemoveAt(path.Count - 1); + } + + [Test] + public void Test() { + TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); + Console.WriteLine("\n二分木を初期化"); + PrintUtil.PrintTree(root); + + // 先行順走査 + PreOrder(root); + + Console.WriteLine("\nルートノードからノード 7 までのすべての経路を出力"); + foreach (List path in res) { + PrintUtil.PrintList(path.Select(p => p.val).ToList()); + } + } +} diff --git a/ja/codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs b/ja/codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs new file mode 100644 index 000000000..041496788 --- /dev/null +++ b/ja/codes/csharp/chapter_backtracking/preorder_traversal_iii_compact.cs @@ -0,0 +1,45 @@ +/** + * File: preorder_traversal_iii_compact.cs + * Created Time: 2023-04-17 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class preorder_traversal_iii_compact { + List path = []; + List> res = []; + + /* 前順走査:例題 3 */ + void PreOrder(TreeNode? root) { + // 枝刈り + if (root == null || root.val == 3) { + return; + } + // 試す + path.Add(root); + if (root.val == 7) { + // 解を記録 + res.Add(new List(path)); + } + PreOrder(root.left); + PreOrder(root.right); + // バックトラック + path.RemoveAt(path.Count - 1); + } + + [Test] + public void Test() { + TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); + Console.WriteLine("\n二分木を初期化"); + PrintUtil.PrintTree(root); + + // 先行順走査 + PreOrder(root); + + Console.WriteLine("\nルートノードからノード 7 までのすべての経路を出力し、経路には値が 3 のノードを含めない"); + foreach (List path in res) { + PrintUtil.PrintList(path.Select(p => p.val).ToList()); + } + } +} diff --git a/ja/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs b/ja/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs new file mode 100644 index 000000000..b4001b3d8 --- /dev/null +++ b/ja/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs @@ -0,0 +1,72 @@ +/** + * File: preorder_traversal_iii_template.cs + * Created Time: 2023-04-17 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_backtracking; + +public class preorder_traversal_iii_template { + /* 現在の状態が解かどうかを判定 */ + bool IsSolution(List state) { + return state.Count != 0 && state[^1].val == 7; + } + + /* 解を記録 */ + void RecordSolution(List state, List> res) { + res.Add(new List(state)); + } + + /* 現在の状態で、この選択が有効かどうかを判定 */ + bool IsValid(List state, TreeNode choice) { + return choice != null && choice.val != 3; + } + + /* 状態を更新 */ + void MakeChoice(List state, TreeNode choice) { + state.Add(choice); + } + + /* 状態を元に戻す */ + void UndoChoice(List state, TreeNode choice) { + state.RemoveAt(state.Count - 1); + } + + /* バックトラッキング:例題 3 */ + void Backtrack(List state, List choices, List> res) { + // 解かどうかを確認 + if (IsSolution(state)) { + // 解を記録 + RecordSolution(state, res); + } + // すべての選択肢を走査 + foreach (TreeNode choice in choices) { + // 枝刈り:選択が妥当かを確認する + if (IsValid(state, choice)) { + // 試行: 選択を行い、状態を更新 + MakeChoice(state, choice); + // 次の選択へ進む + Backtrack(state, [choice.left!, choice.right!], res); + // バックトラック:選択を取り消し、前の状態に戻す + UndoChoice(state, choice); + } + } + } + + [Test] + public void Test() { + TreeNode? root = TreeNode.ListToTree([1, 7, 3, 4, 5, 6, 7]); + Console.WriteLine("\n二分木を初期化"); + PrintUtil.PrintTree(root); + + // バックトラッキング法 + List> res = []; + List choices = [root!]; + Backtrack([], choices, res); + + Console.WriteLine("\nルートノードからノード 7 までのすべての経路を出力し、経路に値が 3 のノードを含まないことを条件とする"); + foreach (List path in res) { + PrintUtil.PrintList(path.Select(p => p.val).ToList()); + } + } +} diff --git a/ja/codes/csharp/chapter_backtracking/subset_sum_i.cs b/ja/codes/csharp/chapter_backtracking/subset_sum_i.cs new file mode 100644 index 000000000..2a3bba9d5 --- /dev/null +++ b/ja/codes/csharp/chapter_backtracking/subset_sum_i.cs @@ -0,0 +1,55 @@ +/** +* File: subset_sum_i.cs +* Created Time: 2023-06-25 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_backtracking; + +public class subset_sum_i { + /* バックトラッキング:部分和 I */ + void Backtrack(List state, int target, int[] choices, int start, List> res) { + // 部分集合の和が target に等しければ、解を記録 + if (target == 0) { + res.Add(new List(state)); + return; + } + // すべての選択肢を走査 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + for (int i = start; i < choices.Length; i++) { + // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため + if (target - choices[i] < 0) { + break; + } + // 試す:選択を行い、target と start を更新 + state.Add(choices[i]); + // 次の選択へ進む + Backtrack(state, target - choices[i], choices, i, res); + // バックトラック:選択を取り消し、前の状態に戻す + state.RemoveAt(state.Count - 1); + } + } + + /* 部分和 I を解く */ + List> SubsetSumI(int[] nums, int target) { + List state = []; // 状態(部分集合) + Array.Sort(nums); // nums をソート + int start = 0; // 開始点を走査 + List> res = []; // 結果リスト(部分集合のリスト) + Backtrack(state, target, nums, start, res); + return res; + } + + [Test] + public void Test() { + int[] nums = [3, 4, 5]; + int target = 9; + List> res = SubsetSumI(nums, target); + Console.WriteLine("入力配列 nums = " + string.Join(", ", nums) + ", target = " + target); + Console.WriteLine("和が " + target + " に等しいすべての部分集合 res = "); + foreach (var subset in res) { + PrintUtil.PrintList(subset); + } + } +} diff --git a/ja/codes/csharp/chapter_backtracking/subset_sum_i_naive.cs b/ja/codes/csharp/chapter_backtracking/subset_sum_i_naive.cs new file mode 100644 index 000000000..176b4991d --- /dev/null +++ b/ja/codes/csharp/chapter_backtracking/subset_sum_i_naive.cs @@ -0,0 +1,53 @@ +/** +* File: subset_sum_i_naive.cs +* Created Time: 2023-06-25 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_backtracking; + +public class subset_sum_i_naive { + /* バックトラッキング:部分和 I */ + void Backtrack(List state, int target, int total, int[] choices, List> res) { + // 部分集合の和が target に等しければ、解を記録 + if (total == target) { + res.Add(new List(state)); + return; + } + // すべての選択肢を走査 + for (int i = 0; i < choices.Length; i++) { + // 枝刈り:部分和が target を超える場合はその選択をスキップする + if (total + choices[i] > target) { + continue; + } + // 試行:選択を行い、要素と total を更新する + state.Add(choices[i]); + // 次の選択へ進む + Backtrack(state, target, total + choices[i], choices, res); + // バックトラック:選択を取り消し、前の状態に戻す + state.RemoveAt(state.Count - 1); + } + } + + /* 部分和 I を解く(重複部分集合を含む) */ + List> SubsetSumINaive(int[] nums, int target) { + List state = []; // 状態(部分集合) + int total = 0; // 部分和 + List> res = []; // 結果リスト(部分集合のリスト) + Backtrack(state, target, total, nums, res); + return res; + } + + [Test] + public void Test() { + int[] nums = [3, 4, 5]; + int target = 9; + List> res = SubsetSumINaive(nums, target); + Console.WriteLine("入力配列 nums = " + string.Join(", ", nums) + ", target = " + target); + Console.WriteLine("和が " + target + " に等しいすべての部分集合 res = "); + foreach (var subset in res) { + PrintUtil.PrintList(subset); + } + Console.WriteLine("この方法の出力結果には重複した集合が含まれることに注意してください"); + } +} diff --git a/ja/codes/csharp/chapter_backtracking/subset_sum_ii.cs b/ja/codes/csharp/chapter_backtracking/subset_sum_ii.cs new file mode 100644 index 000000000..6292b8f37 --- /dev/null +++ b/ja/codes/csharp/chapter_backtracking/subset_sum_ii.cs @@ -0,0 +1,60 @@ +/** +* File: subset_sum_ii.cs +* Created Time: 2023-06-25 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_backtracking; + +public class subset_sum_ii { + /* バックトラッキング:部分和 II */ + void Backtrack(List state, int target, int[] choices, int start, List> res) { + // 部分集合の和が target に等しければ、解を記録 + if (target == 0) { + res.Add(new List(state)); + return; + } + // すべての選択肢を走査 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける + for (int i = start; i < choices.Length; i++) { + // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため + if (target - choices[i] < 0) { + break; + } + // 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // 試す:選択を行い、target と start を更新 + state.Add(choices[i]); + // 次の選択へ進む + Backtrack(state, target - choices[i], choices, i + 1, res); + // バックトラック:選択を取り消し、前の状態に戻す + state.RemoveAt(state.Count - 1); + } + } + + /* 部分和 II を解く */ + List> SubsetSumII(int[] nums, int target) { + List state = []; // 状態(部分集合) + Array.Sort(nums); // nums をソート + int start = 0; // 開始点を走査 + List> res = []; // 結果リスト(部分集合のリスト) + Backtrack(state, target, nums, start, res); + return res; + } + + [Test] + public void Test() { + int[] nums = [4, 4, 5]; + int target = 9; + List> res = SubsetSumII(nums, target); + Console.WriteLine("入力配列 nums = " + string.Join(", ", nums) + ", target = " + target); + Console.WriteLine("和が " + target + " に等しいすべての部分集合 res = "); + foreach (var subset in res) { + PrintUtil.PrintList(subset); + } + } +} diff --git a/ja/codes/csharp/chapter_computational_complexity/iteration.cs b/ja/codes/csharp/chapter_computational_complexity/iteration.cs new file mode 100644 index 000000000..e43638579 --- /dev/null +++ b/ja/codes/csharp/chapter_computational_complexity/iteration.cs @@ -0,0 +1,77 @@ +/** +* File: iteration.cs +* Created Time: 2023-08-28 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_computational_complexity; + +public class iteration { + /* for ループ */ + int ForLoop(int n) { + int res = 0; + // 1, 2, ..., n-1, n を順に加算する + for (int i = 1; i <= n; i++) { + res += i; + } + return res; + } + + /* while ループ */ + int WhileLoop(int n) { + int res = 0; + int i = 1; // 条件変数を初期化する + // 1, 2, ..., n-1, n を順に加算する + while (i <= n) { + res += i; + i += 1; // 条件変数を更新する + } + return res; + } + + /* while ループ(2回更新) */ + int WhileLoopII(int n) { + int res = 0; + int i = 1; // 条件変数を初期化する + // 1, 4, 10, ... を順に加算する + while (i <= n) { + res += i; + // 条件変数を更新する + i += 1; + i *= 2; + } + return res; + } + + /* 二重 for ループ */ + string NestedForLoop(int n) { + StringBuilder res = new(); + // i = 1, 2, ..., n-1, n とループする + for (int i = 1; i <= n; i++) { + // j = 1, 2, ..., n-1, n とループする + for (int j = 1; j <= n; j++) { + res.Append($"({i}, {j}), "); + } + } + return res.ToString(); + } + + /* Driver Code */ + [Test] + public void Test() { + int n = 5; + int res; + + res = ForLoop(n); + Console.WriteLine("\nfor ループの合計結果 res = " + res); + + res = WhileLoop(n); + Console.WriteLine("\nwhile ループの合計結果 res = " + res); + + res = WhileLoopII(n); + Console.WriteLine("\nwhile ループ(2回更新)の合計結果 res = " + res); + + string resStr = NestedForLoop(n); + Console.WriteLine("\n二重 for ループの走査結果 " + resStr); + } +} diff --git a/ja/codes/csharp/chapter_computational_complexity/recursion.cs b/ja/codes/csharp/chapter_computational_complexity/recursion.cs new file mode 100644 index 000000000..61541f5ef --- /dev/null +++ b/ja/codes/csharp/chapter_computational_complexity/recursion.cs @@ -0,0 +1,78 @@ +/** +* File: recursion.cs +* Created Time: 2023-08-28 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_computational_complexity; + +public class recursion { + /* 再帰 */ + int Recur(int n) { + // 終了条件 + if (n == 1) + return 1; + // 再帰:再帰呼び出し + int res = Recur(n - 1); + // 帰りがけ:結果を返す + return n + res; + } + + /* 反復で再帰を模擬する */ + int ForLoopRecur(int n) { + // 明示的なスタックを使ってシステムコールスタックを模擬する + Stack stack = new(); + int res = 0; + // 再帰:再帰呼び出し + for (int i = n; i > 0; i--) { + // 「スタックへのプッシュ」で「再帰」を模擬する + stack.Push(i); + } + // 帰りがけ:結果を返す + while (stack.Count > 0) { + // 「スタックから取り出す操作」で「帰り」をシミュレート + res += stack.Pop(); + } + // res = 1+2+3+...+n + return res; + } + + /* 末尾再帰 */ + int TailRecur(int n, int res) { + // 終了条件 + if (n == 0) + return res; + // 末尾再帰呼び出し + return TailRecur(n - 1, res + n); + } + + /* フィボナッチ数列:再帰 */ + int Fib(int n) { + // 終了条件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1; + // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す + int res = Fib(n - 1) + Fib(n - 2); + // 結果 f(n) を返す + return res; + } + + /* Driver Code */ + [Test] + public void Test() { + int n = 5; + int res; + + res = Recur(n); + Console.WriteLine("\n再帰関数の合計結果 res = " + res); + + res = ForLoopRecur(n); + Console.WriteLine("\n反復で再帰をシミュレートした合計結果 res = " + res); + + res = TailRecur(n, 0); + Console.WriteLine("\n末尾再帰関数の合計結果 res = " + res); + + res = Fib(n); + Console.WriteLine("\nフィボナッチ数列の第 " + n + " 項は " + res); + } +} diff --git a/ja/codes/csharp/chapter_computational_complexity/space_complexity.cs b/ja/codes/csharp/chapter_computational_complexity/space_complexity.cs new file mode 100644 index 000000000..0ed4a067d --- /dev/null +++ b/ja/codes/csharp/chapter_computational_complexity/space_complexity.cs @@ -0,0 +1,104 @@ +/** + * File: space_complexity.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_computational_complexity; + +public class space_complexity { + /* 関数 */ + int Function() { + // 何らかの処理を行う + return 0; + } + + /* 定数階 */ + void Constant(int n) { + // 定数、変数、オブジェクトは O(1) の空間を占める + int a = 0; + int b = 0; + int[] nums = new int[10000]; + ListNode node = new(0); + // ループ内の変数は O(1) の空間を占める + for (int i = 0; i < n; i++) { + int c = 0; + } + // ループ内の関数は O(1) の空間を占める + for (int i = 0; i < n; i++) { + Function(); + } + } + + /* 線形階 */ + void Linear(int n) { + // 長さ n の配列は O(n) の空間を使用 + int[] nums = new int[n]; + // 長さ n のリストは O(n) の空間を使用 + List nodes = []; + for (int i = 0; i < n; i++) { + nodes.Add(new ListNode(i)); + } + // 長さ n のハッシュテーブルは O(n) の空間を使用 + Dictionary map = []; + for (int i = 0; i < n; i++) { + map.Add(i, i.ToString()); + } + } + + /* 線形時間(再帰実装) */ + void LinearRecur(int n) { + Console.WriteLine("再帰 n = " + n); + if (n == 1) return; + LinearRecur(n - 1); + } + + /* 二乗階 */ + void Quadratic(int n) { + // 行列は O(n^2) の空間を使用する + int[,] numMatrix = new int[n, n]; + // 二次元リストは O(n^2) の空間を使用 + List> numList = []; + for (int i = 0; i < n; i++) { + List tmp = []; + for (int j = 0; j < n; j++) { + tmp.Add(0); + } + numList.Add(tmp); + } + } + + /* 二次時間(再帰実装) */ + int QuadraticRecur(int n) { + if (n <= 0) return 0; + int[] nums = new int[n]; + Console.WriteLine("再帰 n = " + n + " における nums の長さ = " + nums.Length); + return QuadraticRecur(n - 1); + } + + /* 指数時間(完全二分木の構築) */ + TreeNode? BuildTree(int n) { + if (n == 0) return null; + TreeNode root = new(0) { + left = BuildTree(n - 1), + right = BuildTree(n - 1) + }; + return root; + } + + [Test] + public void Test() { + int n = 5; + // 定数階 + Constant(n); + // 線形階 + Linear(n); + LinearRecur(n); + // 二乗階 + Quadratic(n); + QuadraticRecur(n); + // 指数オーダー + TreeNode? root = BuildTree(n); + PrintUtil.PrintTree(root); + } +} diff --git a/ja/codes/csharp/chapter_computational_complexity/time_complexity.cs b/ja/codes/csharp/chapter_computational_complexity/time_complexity.cs new file mode 100644 index 000000000..772796c82 --- /dev/null +++ b/ja/codes/csharp/chapter_computational_complexity/time_complexity.cs @@ -0,0 +1,195 @@ +/** + * File: time_complexity.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_computational_complexity; + +public class time_complexity { + void Algorithm(int n) { + int a = 1; // +0(テクニック 1) + a += n; // +0(テクニック 1) + // +n(テクニック 2) + for (int i = 0; i < 5 * n + 1; i++) { + Console.WriteLine(0); + } + // +n*n(テクニック 3) + for (int i = 0; i < 2 * n; i++) { + for (int j = 0; j < n + 1; j++) { + Console.WriteLine(0); + } + } + } + + // アルゴリズム A の時間計算量: 定数時間 + void AlgorithmA(int n) { + Console.WriteLine(0); + } + + // アルゴリズム B の時間計算量: 線形時間 + void AlgorithmB(int n) { + for (int i = 0; i < n; i++) { + Console.WriteLine(0); + } + } + + // アルゴリズム C の時間計算量: 定数時間 + void AlgorithmC(int n) { + for (int i = 0; i < 1000000; i++) { + Console.WriteLine(0); + } + } + + /* 定数階 */ + int Constant(int n) { + int count = 0; + int size = 100000; + for (int i = 0; i < size; i++) + count++; + return count; + } + + /* 線形階 */ + int Linear(int n) { + int count = 0; + for (int i = 0; i < n; i++) + count++; + return count; + } + + /* 線形時間(配列を走査) */ + int ArrayTraversal(int[] nums) { + int count = 0; + // ループ回数は配列長に比例する + foreach (int num in nums) { + count++; + } + return count; + } + + /* 二乗階 */ + int Quadratic(int n) { + int count = 0; + // ループ回数はデータサイズ n の二乗に比例する + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; + } + + /* 二次時間(バブルソート) */ + int BubbleSort(int[] nums) { + int count = 0; // カウンタ + // 外側のループ:未ソート区間は [0, i] + for (int i = nums.Length - 1; i > 0; i--) { + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // nums[j] と nums[j + 1] を交換 + (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); + count += 3; // 要素交換には 3 回の単位操作が含まれる + } + } + } + return count; + } + + /* 指数時間(ループ実装) */ + int Exponential(int n) { + int count = 0, bas = 1; + // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する + for (int i = 0; i < n; i++) { + for (int j = 0; j < bas; j++) { + count++; + } + bas *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; + } + + /* 指数時間(再帰実装) */ + int ExpRecur(int n) { + if (n == 1) return 1; + return ExpRecur(n - 1) + ExpRecur(n - 1) + 1; + } + + /* 対数時間(ループ実装) */ + int Logarithmic(int n) { + int count = 0; + while (n > 1) { + n /= 2; + count++; + } + return count; + } + + /* 対数時間(再帰実装) */ + int LogRecur(int n) { + if (n <= 1) return 0; + return LogRecur(n / 2) + 1; + } + + /* 線形対数時間 */ + int LinearLogRecur(int n) { + if (n <= 1) return 1; + int count = LinearLogRecur(n / 2) + LinearLogRecur(n / 2); + for (int i = 0; i < n; i++) { + count++; + } + return count; + } + + /* 階乗時間(再帰実装) */ + int FactorialRecur(int n) { + if (n == 0) return 1; + int count = 0; + // 1個から n 個に分裂 + for (int i = 0; i < n; i++) { + count += FactorialRecur(n - 1); + } + return count; + } + + [Test] + public void Test() { + // n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる + int n = 8; + Console.WriteLine("入力データサイズ n = " + n); + + int count = Constant(n); + Console.WriteLine("定数時間の操作回数 = " + count); + + count = Linear(n); + Console.WriteLine("線形時間の操作回数 = " + count); + count = ArrayTraversal(new int[n]); + Console.WriteLine("線形時間(配列の走査)の操作回数 = " + count); + + count = Quadratic(n); + Console.WriteLine("二乗時間の操作回数 = " + count); + int[] nums = new int[n]; + for (int i = 0; i < n; i++) + nums[i] = n - i; // [n,n-1,...,2,1] + count = BubbleSort(nums); + Console.WriteLine("二乗時間(バブルソート)の操作回数 = " + count); + + count = Exponential(n); + Console.WriteLine("指数時間(ループ実装)の操作回数 = " + count); + count = ExpRecur(n); + Console.WriteLine("指数時間(再帰実装)の操作回数 = " + count); + + count = Logarithmic(n); + Console.WriteLine("対数時間(ループ実装)の操作回数 = " + count); + count = LogRecur(n); + Console.WriteLine("対数時間(再帰実装)の操作回数 = " + count); + + count = LinearLogRecur(n); + Console.WriteLine("線形対数時間(再帰実装)の操作回数 = " + count); + + count = FactorialRecur(n); + Console.WriteLine("階乗時間(再帰実装)の操作回数 = " + count); + } +} diff --git a/ja/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs b/ja/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs new file mode 100644 index 000000000..ea1e5a8f7 --- /dev/null +++ b/ja/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs @@ -0,0 +1,49 @@ +/** + * File: worst_best_time_complexity.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_computational_complexity; + +public class worst_best_time_complexity { + /* 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 */ + int[] RandomNumbers(int n) { + int[] nums = new int[n]; + // 配列 nums = { 1, 2, 3, ..., n } を生成 + for (int i = 0; i < n; i++) { + nums[i] = i + 1; + } + + // 配列要素をランダムにシャッフル + for (int i = 0; i < nums.Length; i++) { + int index = new Random().Next(i, nums.Length); + (nums[i], nums[index]) = (nums[index], nums[i]); + } + return nums; + } + + /* 配列 nums 内で数値 1 のインデックスを探す */ + int FindOne(int[] nums) { + for (int i = 0; i < nums.Length; i++) { + // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる + // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる + if (nums[i] == 1) + return i; + } + return -1; + } + + + /* Driver Code */ + [Test] + public void Test() { + for (int i = 0; i < 10; i++) { + int n = 100; + int[] nums = RandomNumbers(n); + int index = FindOne(nums); + Console.WriteLine("\n配列 [ 1, 2, ..., n ] をシャッフルした後 = " + string.Join(",", nums)); + Console.WriteLine("数字 1 のインデックスは " + index); + } + } +} diff --git a/ja/codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs b/ja/codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs new file mode 100644 index 000000000..d77f17af5 --- /dev/null +++ b/ja/codes/csharp/chapter_divide_and_conquer/binary_search_recur.cs @@ -0,0 +1,46 @@ +/** +* File: binary_search_recur.cs +* Created Time: 2023-07-18 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_divide_and_conquer; + +public class binary_search_recur { + /* 二分探索:問題 f(i, j) */ + int DFS(int[] nums, int target, int i, int j) { + // 区間が空なら対象要素は存在しないので -1 を返す + if (i > j) { + return -1; + } + // 中点インデックス m を計算 + int m = (i + j) / 2; + if (nums[m] < target) { + // 部分問題 f(m+1, j) を再帰的に解く + return DFS(nums, target, m + 1, j); + } else if (nums[m] > target) { + // 部分問題 f(i, m-1) を再帰的に解く + return DFS(nums, target, i, m - 1); + } else { + // 目標要素が見つかったらそのインデックスを返す + return m; + } + } + + /* 二分探索 */ + int BinarySearch(int[] nums, int target) { + int n = nums.Length; + // 問題 f(0, n-1) を解く + return DFS(nums, target, 0, n - 1); + } + + [Test] + public void Test() { + int target = 6; + int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + // 二分探索(両閉区間) + int index = BinarySearch(nums, target); + Console.WriteLine("対象要素 6 のインデックス = " + index); + } +} diff --git a/ja/codes/csharp/chapter_divide_and_conquer/build_tree.cs b/ja/codes/csharp/chapter_divide_and_conquer/build_tree.cs new file mode 100644 index 000000000..4d29743a0 --- /dev/null +++ b/ja/codes/csharp/chapter_divide_and_conquer/build_tree.cs @@ -0,0 +1,49 @@ +/** +* File: build_tree.cs +* Created Time: 2023-07-18 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_divide_and_conquer; + +public class build_tree { + /* 二分木を構築:分割統治 */ + TreeNode? DFS(int[] preorder, Dictionary inorderMap, int i, int l, int r) { + // 部分木区間が空なら終了する + if (r - l < 0) + return null; + // ルートノードを初期化する + TreeNode root = new(preorder[i]); + // m を求めて左右部分木を分割する + int m = inorderMap[preorder[i]]; + // 部分問題:左部分木を構築する + root.left = DFS(preorder, inorderMap, i + 1, l, m - 1); + // 部分問題:右部分木を構築する + root.right = DFS(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 根ノードを返す + return root; + } + + /* 二分木を構築 */ + TreeNode? BuildTree(int[] preorder, int[] inorder) { + // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する + Dictionary inorderMap = []; + for (int i = 0; i < inorder.Length; i++) { + inorderMap.TryAdd(inorder[i], i); + } + TreeNode? root = DFS(preorder, inorderMap, 0, 0, inorder.Length - 1); + return root; + } + + [Test] + public void Test() { + int[] preorder = [3, 9, 2, 1, 7]; + int[] inorder = [9, 3, 1, 2, 7]; + Console.WriteLine("前順走査 = " + string.Join(", ", preorder)); + Console.WriteLine("中順走査 = " + string.Join(", ", inorder)); + + TreeNode? root = BuildTree(preorder, inorder); + Console.WriteLine("構築した二分木は次のとおりです:"); + PrintUtil.PrintTree(root); + } +} diff --git a/ja/codes/csharp/chapter_divide_and_conquer/hanota.cs b/ja/codes/csharp/chapter_divide_and_conquer/hanota.cs new file mode 100644 index 000000000..a6762f3a3 --- /dev/null +++ b/ja/codes/csharp/chapter_divide_and_conquer/hanota.cs @@ -0,0 +1,59 @@ +/** +* File: hanota.cs +* Created Time: 2023-07-18 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_divide_and_conquer; + +public class hanota { + /* 円盤を 1 枚移動 */ + void Move(List src, List tar) { + // src の上から円盤を1枚取り出す + int pan = src[^1]; + src.RemoveAt(src.Count - 1); + // 円盤を tar の上に置く + tar.Add(pan); + } + + /* ハノイの塔の問題 f(i) を解く */ + void DFS(int i, List src, List buf, List tar) { + // src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す + if (i == 1) { + Move(src, tar); + return; + } + // 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す + DFS(i - 1, src, tar, buf); + // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す + Move(src, tar); + // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す + DFS(i - 1, buf, src, tar); + } + + /* ハノイの塔を解く */ + void SolveHanota(List A, List B, List C) { + int n = A.Count; + // A の上から n 枚の円盤を B を介して C へ移す + DFS(n, A, B, C); + } + + [Test] + public void Test() { + // リスト末尾が柱の頂上 + List A = [5, 4, 3, 2, 1]; + List B = []; + List C = []; + Console.WriteLine("初期状態:"); + Console.WriteLine("A = " + string.Join(", ", A)); + Console.WriteLine("B = " + string.Join(", ", B)); + Console.WriteLine("C = " + string.Join(", ", C)); + + SolveHanota(A, B, C); + + Console.WriteLine("円盤の移動完了後:"); + Console.WriteLine("A = " + string.Join(", ", A)); + Console.WriteLine("B = " + string.Join(", ", B)); + Console.WriteLine("C = " + string.Join(", ", C)); + } +} diff --git a/ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs b/ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs new file mode 100644 index 000000000..809c011b9 --- /dev/null +++ b/ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_backtrack.cs @@ -0,0 +1,41 @@ +/** +* File: climbing_stairs_backtrack.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_backtrack { + /* バックトラッキング */ + void Backtrack(List choices, int state, int n, List res) { + // 第 n 段に到達したら、方法数を 1 増やす + if (state == n) + res[0]++; + // すべての選択肢を走査 + foreach (int choice in choices) { + // 枝刈り: 第 n 段を超えないようにする + if (state + choice > n) + continue; + // 試行: 選択を行い、状態を更新 + Backtrack(choices, state + choice, n, res); + // バックトラック + } + } + + /* 階段登り:バックトラッキング */ + int ClimbingStairsBacktrack(int n) { + List choices = [1, 2]; // 1 段または 2 段上ることを選べる + int state = 0; // 第 0 段から上り始める + List res = [0]; // res[0] を使って方法数を記録する + Backtrack(choices, state, n, res); + return res[0]; + } + + [Test] + public void Test() { + int n = 9; + int res = ClimbingStairsBacktrack(n); + Console.WriteLine($"{n} 段の階段を上る方法は全部で {res} 通り"); + } +} diff --git a/ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs b/ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs new file mode 100644 index 000000000..9df6aaa83 --- /dev/null +++ b/ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cs @@ -0,0 +1,36 @@ +/** +* File: climbing_stairs_constraint_dp.cs +* Created Time: 2023-07-03 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_constraint_dp { + /* 制約付き階段登り:動的計画法 */ + int ClimbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 部分問題の解を保存するために dp テーブルを初期化 + int[,] dp = new int[n + 1, 3]; + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1, 1] = 1; + dp[1, 2] = 0; + dp[2, 1] = 0; + dp[2, 2] = 1; + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for (int i = 3; i <= n; i++) { + dp[i, 1] = dp[i - 1, 2]; + dp[i, 2] = dp[i - 2, 1] + dp[i - 2, 2]; + } + return dp[n, 1] + dp[n, 2]; + } + + [Test] + public void Test() { + int n = 9; + int res = ClimbingStairsConstraintDP(n); + Console.WriteLine($"{n} 段の階段を上る方法は全部で {res} 通り"); + } +} diff --git a/ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs b/ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs new file mode 100644 index 000000000..cebc187fa --- /dev/null +++ b/ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs.cs @@ -0,0 +1,31 @@ +/** +* File: climbing_stairs_dfs.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_dfs { + /* 検索 */ + int DFS(int i) { + // dp[1] と dp[2] は既知なので返す + if (i == 1 || i == 2) + return i; + // dp[i] = dp[i-1] + dp[i-2] + int count = DFS(i - 1) + DFS(i - 2); + return count; + } + + /* 階段登り:探索 */ + int ClimbingStairsDFS(int n) { + return DFS(n); + } + + [Test] + public void Test() { + int n = 9; + int res = ClimbingStairsDFS(n); + Console.WriteLine($"{n} 段の階段を上る方法は全部で {res} 通り"); + } +} diff --git a/ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs b/ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs new file mode 100644 index 000000000..f42061647 --- /dev/null +++ b/ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cs @@ -0,0 +1,39 @@ +/** +* File: climbing_stairs_dfs_mem.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_dfs_mem { + /* メモ化探索 */ + int DFS(int i, int[] mem) { + // dp[1] と dp[2] は既知なので返す + if (i == 1 || i == 2) + return i; + // dp[i] の記録があれば、それをそのまま返す + if (mem[i] != -1) + return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + int count = DFS(i - 1, mem) + DFS(i - 2, mem); + // dp[i] を記録する + mem[i] = count; + return count; + } + + /* 階段登り:メモ化探索 */ + int ClimbingStairsDFSMem(int n) { + // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す + int[] mem = new int[n + 1]; + Array.Fill(mem, -1); + return DFS(n, mem); + } + + [Test] + public void Test() { + int n = 9; + int res = ClimbingStairsDFSMem(n); + Console.WriteLine($"{n} 段の階段を上る方法は全部で {res} 通り"); + } +} diff --git a/ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs b/ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs new file mode 100644 index 000000000..a771c416c --- /dev/null +++ b/ja/codes/csharp/chapter_dynamic_programming/climbing_stairs_dp.cs @@ -0,0 +1,49 @@ +/** +* File: climbing_stairs_dp.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class climbing_stairs_dp { + /* 階段登り:動的計画法 */ + int ClimbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // 部分問題の解を保存するために dp テーブルを初期化 + int[] dp = new int[n + 1]; + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1] = 1; + dp[2] = 2; + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; + } + + /* 階段登り:空間最適化した動的計画法 */ + int ClimbingStairsDPComp(int n) { + if (n == 1 || n == 2) + return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; + } + + [Test] + public void Test() { + int n = 9; + + int res = ClimbingStairsDP(n); + Console.WriteLine($"{n} 段の階段を上る方法は全部で {res} 通り"); + + res = ClimbingStairsDPComp(n); + Console.WriteLine($"{n} 段の階段を上る方法は全部で {res} 通り"); + } +} diff --git a/ja/codes/csharp/chapter_dynamic_programming/coin_change.cs b/ja/codes/csharp/chapter_dynamic_programming/coin_change.cs new file mode 100644 index 000000000..5bbbcadf6 --- /dev/null +++ b/ja/codes/csharp/chapter_dynamic_programming/coin_change.cs @@ -0,0 +1,71 @@ +/** +* File: coin_change.cs +* Created Time: 2023-07-12 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class coin_change { + /* コイン両替:動的計画法 */ + int CoinChangeDP(int[] coins, int amt) { + int n = coins.Length; + int MAX = amt + 1; + // dp テーブルを初期化 + int[,] dp = new int[n + 1, amt + 1]; + // 状態遷移:先頭行と先頭列 + for (int a = 1; a <= amt; a++) { + dp[0, a] = MAX; + } + // 状態遷移: 残りの行と列 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[i, a] = dp[i - 1, a]; + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[i, a] = Math.Min(dp[i - 1, a], dp[i, a - coins[i - 1]] + 1); + } + } + } + return dp[n, amt] != MAX ? dp[n, amt] : -1; + } + + /* コイン交換:空間最適化後の動的計画法 */ + int CoinChangeDPComp(int[] coins, int amt) { + int n = coins.Length; + int MAX = amt + 1; + // dp テーブルを初期化 + int[] dp = new int[amt + 1]; + Array.Fill(dp, MAX); + dp[0] = 0; + // 状態遷移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a]; + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[a] = Math.Min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; + } + + [Test] + public void Test() { + int[] coins = [1, 2, 5]; + int amt = 4; + + // 動的計画法 + int res = CoinChangeDP(coins, amt); + Console.WriteLine("目標金額にするために必要な最小の硬貨枚数は " + res); + + // 空間最適化後の動的計画法 + res = CoinChangeDPComp(coins, amt); + Console.WriteLine("目標金額にするために必要な最小の硬貨枚数は " + res); + } +} diff --git a/ja/codes/csharp/chapter_dynamic_programming/coin_change_ii.cs b/ja/codes/csharp/chapter_dynamic_programming/coin_change_ii.cs new file mode 100644 index 000000000..857b17f01 --- /dev/null +++ b/ja/codes/csharp/chapter_dynamic_programming/coin_change_ii.cs @@ -0,0 +1,68 @@ +/** +* File: coin_change_ii.cs +* Created Time: 2023-07-12 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class coin_change_ii { + /* コイン両替 II:動的計画法 */ + int CoinChangeIIDP(int[] coins, int amt) { + int n = coins.Length; + // dp テーブルを初期化 + int[,] dp = new int[n + 1, amt + 1]; + // 先頭列を初期化する + for (int i = 0; i <= n; i++) { + dp[i, 0] = 1; + } + // 状態遷移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[i, a] = dp[i - 1, a]; + } else { + // コイン i を選ばない場合と選ぶ場合の和 + dp[i, a] = dp[i - 1, a] + dp[i, a - coins[i - 1]]; + } + } + } + return dp[n, amt]; + } + + /* コイン両替 II:空間最適化した動的計画法 */ + int CoinChangeIIDPComp(int[] coins, int amt) { + int n = coins.Length; + // dp テーブルを初期化 + int[] dp = new int[amt + 1]; + dp[0] = 1; + // 状態遷移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a]; + } else { + // コイン i を選ばない場合と選ぶ場合の和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; + } + + [Test] + public void Test() { + int[] coins = [1, 2, 5]; + int amt = 5; + + // 動的計画法 + int res = CoinChangeIIDP(coins, amt); + Console.WriteLine("目標金額を作る硬貨の組み合わせ数は " + res); + + // 空間最適化後の動的計画法 + res = CoinChangeIIDPComp(coins, amt); + Console.WriteLine("目標金額を作る硬貨の組み合わせ数は " + res); + } +} diff --git a/ja/codes/csharp/chapter_dynamic_programming/edit_distance.cs b/ja/codes/csharp/chapter_dynamic_programming/edit_distance.cs new file mode 100644 index 000000000..23e10943f --- /dev/null +++ b/ja/codes/csharp/chapter_dynamic_programming/edit_distance.cs @@ -0,0 +1,141 @@ +/** +* File: edit_distance.cs +* Created Time: 2023-07-14 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class edit_distance { + /* 編集距離:総当たり探索 */ + int EditDistanceDFS(string s, string t, int i, int j) { + // s と t がともに空なら 0 を返す + if (i == 0 && j == 0) + return 0; + // s が空なら t の長さを返す + if (i == 0) + return j; + // t が空なら s の長さを返す + if (j == 0) + return i; + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if (s[i - 1] == t[j - 1]) + return EditDistanceDFS(s, t, i - 1, j - 1); + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + int insert = EditDistanceDFS(s, t, i, j - 1); + int delete = EditDistanceDFS(s, t, i - 1, j); + int replace = EditDistanceDFS(s, t, i - 1, j - 1); + // 最小編集回数を返す + return Math.Min(Math.Min(insert, delete), replace) + 1; + } + + /* 編集距離:メモ化探索 */ + int EditDistanceDFSMem(string s, string t, int[][] mem, int i, int j) { + // s と t がともに空なら 0 を返す + if (i == 0 && j == 0) + return 0; + // s が空なら t の長さを返す + if (i == 0) + return j; + // t が空なら s の長さを返す + if (j == 0) + return i; + // 記録済みなら、それをそのまま返す + if (mem[i][j] != -1) + return mem[i][j]; + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if (s[i - 1] == t[j - 1]) + return EditDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + int insert = EditDistanceDFSMem(s, t, mem, i, j - 1); + int delete = EditDistanceDFSMem(s, t, mem, i - 1, j); + int replace = EditDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 最小編集回数を記録して返す + mem[i][j] = Math.Min(Math.Min(insert, delete), replace) + 1; + return mem[i][j]; + } + + /* 編集距離:動的計画法 */ + int EditDistanceDP(string s, string t) { + int n = s.Length, m = t.Length; + int[,] dp = new int[n + 1, m + 1]; + // 状態遷移:先頭行と先頭列 + for (int i = 1; i <= n; i++) { + dp[i, 0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0, j] = j; + } + // 状態遷移: 残りの行と列 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s[i - 1] == t[j - 1]) { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[i, j] = dp[i - 1, j - 1]; + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[i, j] = Math.Min(Math.Min(dp[i, j - 1], dp[i - 1, j]), dp[i - 1, j - 1]) + 1; + } + } + } + return dp[n, m]; + } + + /* 編集距離:空間最適化した動的計画法 */ + int EditDistanceDPComp(string s, string t) { + int n = s.Length, m = t.Length; + int[] dp = new int[m + 1]; + // 状態遷移:先頭行 + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // 状態遷移:残りの行 + for (int i = 1; i <= n; i++) { + // 状態遷移:先頭列 + int leftup = dp[0]; // dp[i-1, j-1] を一時保存する + dp[0] = i; + // 状態遷移:残りの列 + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[j] = leftup; + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[j] = Math.Min(Math.Min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 次の反復の dp[i-1, j-1] に更新する + } + } + return dp[m]; + } + + [Test] + public void Test() { + string s = "bag"; + string t = "pack"; + int n = s.Length, m = t.Length; + + // 全探索 + int res = EditDistanceDFS(s, t, n, m); + Console.WriteLine("" + s + " を " + t + " に変更するには少なくとも " + res + " 回の編集が必要"); + + // メモ化探索 + int[][] mem = new int[n + 1][]; + for (int i = 0; i <= n; i++) { + mem[i] = new int[m + 1]; + Array.Fill(mem[i], -1); + } + + res = EditDistanceDFSMem(s, t, mem, n, m); + Console.WriteLine("" + s + " を " + t + " に変更するには少なくとも " + res + " 回の編集が必要"); + + // 動的計画法 + res = EditDistanceDP(s, t); + Console.WriteLine("" + s + " を " + t + " に変更するには少なくとも " + res + " 回の編集が必要"); + + // 空間最適化後の動的計画法 + res = EditDistanceDPComp(s, t); + Console.WriteLine("" + s + " を " + t + " に変更するには少なくとも " + res + " 回の編集が必要"); + } +} diff --git a/ja/codes/csharp/chapter_dynamic_programming/knapsack.cs b/ja/codes/csharp/chapter_dynamic_programming/knapsack.cs new file mode 100644 index 000000000..43c286ed2 --- /dev/null +++ b/ja/codes/csharp/chapter_dynamic_programming/knapsack.cs @@ -0,0 +1,118 @@ +/** +* File: knapsack.cs +* Created Time: 2023-07-07 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class knapsack { + /* 0-1 ナップサック:総当たり探索 */ + int KnapsackDFS(int[] weight, int[] val, int i, int c) { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if (i == 0 || c == 0) { + return 0; + } + // ナップサック容量を超える場合は、入れない選択しかできない + if (weight[i - 1] > c) { + return KnapsackDFS(weight, val, i - 1, c); + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + int no = KnapsackDFS(weight, val, i - 1, c); + int yes = KnapsackDFS(weight, val, i - 1, c - weight[i - 1]) + val[i - 1]; + // 2つの案のうち価値が大きいほうを返す + return Math.Max(no, yes); + } + + /* 0-1 ナップサック:メモ化探索 */ + int KnapsackDFSMem(int[] weight, int[] val, int[][] mem, int i, int c) { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if (i == 0 || c == 0) { + return 0; + } + // 既に記録があればそのまま返す + if (mem[i][c] != -1) { + return mem[i][c]; + } + // ナップサック容量を超える場合は、入れない選択しかできない + if (weight[i - 1] > c) { + return KnapsackDFSMem(weight, val, mem, i - 1, c); + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + int no = KnapsackDFSMem(weight, val, mem, i - 1, c); + int yes = KnapsackDFSMem(weight, val, mem, i - 1, c - weight[i - 1]) + val[i - 1]; + // 2 つの案のうち価値が大きい方を記録して返す + mem[i][c] = Math.Max(no, yes); + return mem[i][c]; + } + + /* 0-1 ナップサック:動的計画法 */ + int KnapsackDP(int[] weight, int[] val, int cap) { + int n = weight.Length; + // dp テーブルを初期化 + int[,] dp = new int[n + 1, cap + 1]; + // 状態遷移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (weight[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[i, c] = dp[i - 1, c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i, c] = Math.Max(dp[i - 1, c - weight[i - 1]] + val[i - 1], dp[i - 1, c]); + } + } + } + return dp[n, cap]; + } + + /* 0-1 ナップサック:空間最適化後の動的計画法 */ + int KnapsackDPComp(int[] weight, int[] val, int cap) { + int n = weight.Length; + // dp テーブルを初期化 + int[] dp = new int[cap + 1]; + // 状態遷移 + for (int i = 1; i <= n; i++) { + // 逆順に走査する + for (int c = cap; c > 0; c--) { + if (weight[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[c] = dp[c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = Math.Max(dp[c], dp[c - weight[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + + [Test] + public void Test() { + int[] weight = [10, 20, 30, 40, 50]; + int[] val = [50, 120, 150, 210, 240]; + int cap = 50; + int n = weight.Length; + + // 全探索 + int res = KnapsackDFS(weight, val, n, cap); + Console.WriteLine("バックパック容量を超えない最大価値は " + res); + + // メモ化探索 + int[][] mem = new int[n + 1][]; + for (int i = 0; i <= n; i++) { + mem[i] = new int[cap + 1]; + Array.Fill(mem[i], -1); + } + res = KnapsackDFSMem(weight, val, mem, n, cap); + Console.WriteLine("バックパック容量を超えない最大価値は " + res); + + // 動的計画法 + res = KnapsackDP(weight, val, cap); + Console.WriteLine("バックパック容量を超えない最大価値は " + res); + + // 空間最適化後の動的計画法 + res = KnapsackDPComp(weight, val, cap); + Console.WriteLine("バックパック容量を超えない最大価値は " + res); + } +} diff --git a/ja/codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs b/ja/codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs new file mode 100644 index 000000000..ae9979092 --- /dev/null +++ b/ja/codes/csharp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cs @@ -0,0 +1,53 @@ +/** +* File: min_cost_climbing_stairs_dp.cs +* Created Time: 2023-06-30 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class min_cost_climbing_stairs_dp { + /* 階段登りの最小コスト:動的計画法 */ + int MinCostClimbingStairsDP(int[] cost) { + int n = cost.Length - 1; + if (n == 1 || n == 2) + return cost[n]; + // 部分問題の解を保存するために dp テーブルを初期化 + int[] dp = new int[n + 1]; + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for (int i = 3; i <= n; i++) { + dp[i] = Math.Min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; + } + + /* 階段昇りの最小コスト:空間最適化後の動的計画法 */ + int MinCostClimbingStairsDPComp(int[] cost) { + int n = cost.Length - 1; + if (n == 1 || n == 2) + return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = Math.Min(a, tmp) + cost[i]; + a = tmp; + } + return b; + } + + [Test] + public void Test() { + int[] cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; + Console.WriteLine("入力された階段コストのリストは"); + PrintUtil.PrintList(cost); + + int res = MinCostClimbingStairsDP(cost); + Console.WriteLine($"階段を上り切る最小コストは {res}"); + + res = MinCostClimbingStairsDPComp(cost); + Console.WriteLine($"階段を上り切る最小コストは {res}"); + } +} diff --git a/ja/codes/csharp/chapter_dynamic_programming/min_path_sum.cs b/ja/codes/csharp/chapter_dynamic_programming/min_path_sum.cs new file mode 100644 index 000000000..4bacc752d --- /dev/null +++ b/ja/codes/csharp/chapter_dynamic_programming/min_path_sum.cs @@ -0,0 +1,127 @@ +/** +* File: min_path_sum.cs +* Created Time: 2023-07-10 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class min_path_sum { + /* 最小経路和:全探索 */ + int MinPathSumDFS(int[][] grid, int i, int j) { + // 左上のセルなら探索を終了する + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if (i < 0 || j < 0) { + return int.MaxValue; + } + // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する + int up = MinPathSumDFS(grid, i - 1, j); + int left = MinPathSumDFS(grid, i, j - 1); + // 左上隅から (i, j) までの最小経路コストを返す + return Math.Min(left, up) + grid[i][j]; + } + + /* 最小経路和:メモ化探索 */ + int MinPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { + // 左上のセルなら探索を終了する + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if (i < 0 || j < 0) { + return int.MaxValue; + } + // 既に記録があればそのまま返す + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左と上のセルからの最小経路コスト + int up = MinPathSumDFSMem(grid, mem, i - 1, j); + int left = MinPathSumDFSMem(grid, mem, i, j - 1); + // 左上から (i, j) までの最小経路コストを記録して返す + mem[i][j] = Math.Min(left, up) + grid[i][j]; + return mem[i][j]; + } + + /* 最小経路和:動的計画法 */ + int MinPathSumDP(int[][] grid) { + int n = grid.Length, m = grid[0].Length; + // dp テーブルを初期化 + int[,] dp = new int[n, m]; + dp[0, 0] = grid[0][0]; + // 状態遷移:先頭行 + for (int j = 1; j < m; j++) { + dp[0, j] = dp[0, j - 1] + grid[0][j]; + } + // 状態遷移:先頭列 + for (int i = 1; i < n; i++) { + dp[i, 0] = dp[i - 1, 0] + grid[i][0]; + } + // 状態遷移: 残りの行と列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i, j] = Math.Min(dp[i, j - 1], dp[i - 1, j]) + grid[i][j]; + } + } + return dp[n - 1, m - 1]; + } + + /* 最小経路和:空間最適化後の動的計画法 */ + int MinPathSumDPComp(int[][] grid) { + int n = grid.Length, m = grid[0].Length; + // dp テーブルを初期化 + int[] dp = new int[m]; + dp[0] = grid[0][0]; + // 状態遷移:先頭行 + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状態遷移:残りの行 + for (int i = 1; i < n; i++) { + // 状態遷移:先頭列 + dp[0] = dp[0] + grid[i][0]; + // 状態遷移:残りの列 + for (int j = 1; j < m; j++) { + dp[j] = Math.Min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; + } + + [Test] + public void Test() { + int[][] grid = + [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2] + ]; + + int n = grid.Length, m = grid[0].Length; + + // 全探索 + int res = MinPathSumDFS(grid, n - 1, m - 1); + Console.WriteLine("左上から右下までの最小経路和は " + res); + + // メモ化探索 + int[][] mem = new int[n][]; + for (int i = 0; i < n; i++) { + mem[i] = new int[m]; + Array.Fill(mem[i], -1); + } + res = MinPathSumDFSMem(grid, mem, n - 1, m - 1); + Console.WriteLine("左上から右下までの最小経路和は " + res); + + // 動的計画法 + res = MinPathSumDP(grid); + Console.WriteLine("左上から右下までの最小経路和は " + res); + + // 空間最適化後の動的計画法 + res = MinPathSumDPComp(grid); + Console.WriteLine("左上から右下までの最小経路和は " + res); + } +} diff --git a/ja/codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs b/ja/codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs new file mode 100644 index 000000000..878483d6d --- /dev/null +++ b/ja/codes/csharp/chapter_dynamic_programming/unbounded_knapsack.cs @@ -0,0 +1,64 @@ +/** +* File: unbounded_knapsack.cs +* Created Time: 2023-07-12 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_dynamic_programming; + +public class unbounded_knapsack { + /* 完全ナップサック問題:動的計画法 */ + int UnboundedKnapsackDP(int[] wgt, int[] val, int cap) { + int n = wgt.Length; + // dp テーブルを初期化 + int[,] dp = new int[n + 1, cap + 1]; + // 状態遷移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[i, c] = dp[i - 1, c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i, c] = Math.Max(dp[i - 1, c], dp[i, c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n, cap]; + } + + /* 完全ナップサック問題:空間最適化後の動的計画法 */ + int UnboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { + int n = wgt.Length; + // dp テーブルを初期化 + int[] dp = new int[cap + 1]; + // 状態遷移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[c] = dp[c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = Math.Max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; + } + + [Test] + public void Test() { + int[] wgt = [1, 2, 3]; + int[] val = [5, 11, 15]; + int cap = 4; + + // 動的計画法 + int res = UnboundedKnapsackDP(wgt, val, cap); + Console.WriteLine("バックパック容量を超えない最大価値は " + res); + + // 空間最適化後の動的計画法 + res = UnboundedKnapsackDPComp(wgt, val, cap); + Console.WriteLine("バックパック容量を超えない最大価値は " + res); + } +} diff --git a/ja/codes/csharp/chapter_graph/graph_adjacency_list.cs b/ja/codes/csharp/chapter_graph/graph_adjacency_list.cs new file mode 100644 index 000000000..8d28ad569 --- /dev/null +++ b/ja/codes/csharp/chapter_graph/graph_adjacency_list.cs @@ -0,0 +1,122 @@ +/** + * File: graph_adjacency_list.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com) + */ + +namespace hello_algo.chapter_graph; + +/* 隣接リストに基づく無向グラフクラス */ +public class GraphAdjList { + // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 + public Dictionary> adjList; + + /* コンストラクタ */ + public GraphAdjList(Vertex[][] edges) { + adjList = []; + // すべての頂点と辺を追加 + foreach (Vertex[] edge in edges) { + AddVertex(edge[0]); + AddVertex(edge[1]); + AddEdge(edge[0], edge[1]); + } + } + + /* 頂点数を取得 */ + int Size() { + return adjList.Count; + } + + /* 辺を追加 */ + public void AddEdge(Vertex vet1, Vertex vet2) { + if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) + throw new InvalidOperationException(); + // 辺 vet1 - vet2 を追加 + adjList[vet1].Add(vet2); + adjList[vet2].Add(vet1); + } + + /* 辺を削除 */ + public void RemoveEdge(Vertex vet1, Vertex vet2) { + if (!adjList.ContainsKey(vet1) || !adjList.ContainsKey(vet2) || vet1 == vet2) + throw new InvalidOperationException(); + // 辺 vet1 - vet2 を削除 + adjList[vet1].Remove(vet2); + adjList[vet2].Remove(vet1); + } + + /* 頂点を追加 */ + public void AddVertex(Vertex vet) { + if (adjList.ContainsKey(vet)) + return; + // 隣接リストに新しいリストを追加 + adjList.Add(vet, []); + } + + /* 頂点を削除 */ + public void RemoveVertex(Vertex vet) { + if (!adjList.ContainsKey(vet)) + throw new InvalidOperationException(); + // 隣接リストから頂点 vet に対応するリストを削除 + adjList.Remove(vet); + // 他の頂点のリストを走査し、vet を含むすべての辺を削除 + foreach (List list in adjList.Values) { + list.Remove(vet); + } + } + + /* 隣接リストを出力 */ + public void Print() { + Console.WriteLine("隣接リスト ="); + foreach (KeyValuePair> pair in adjList) { + List tmp = []; + foreach (Vertex vertex in pair.Value) + tmp.Add(vertex.val); + Console.WriteLine(pair.Key.val + ": [" + string.Join(", ", tmp) + "],"); + } + } +} + +public class graph_adjacency_list { + [Test] + public void Test() { + /* 無向グラフを初期化 */ + Vertex[] v = Vertex.ValsToVets([1, 3, 2, 5, 4]); + Vertex[][] edges = + [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]] + ]; + GraphAdjList graph = new(edges); + Console.WriteLine("\n初期化後、グラフは"); + graph.Print(); + + /* 辺を追加 */ + // 頂点 1, 2 は v[0], v[2] + graph.AddEdge(v[0], v[2]); + Console.WriteLine("\n辺 1-2 を追加した後、グラフは"); + graph.Print(); + + /* 辺を削除 */ + // 頂点 1, 3 は v[0], v[1] + graph.RemoveEdge(v[0], v[1]); + Console.WriteLine("\n辺 1-3 を削除した後、グラフは"); + graph.Print(); + + /* 頂点を追加 */ + Vertex v5 = new(6); + graph.AddVertex(v5); + Console.WriteLine("\n頂点 6 を追加した後、グラフは"); + graph.Print(); + + /* 頂点を削除 */ + // 頂点 3 は v[1] + graph.RemoveVertex(v[1]); + Console.WriteLine("\n頂点 3 を削除した後、グラフは"); + graph.Print(); + } +} diff --git a/ja/codes/csharp/chapter_graph/graph_adjacency_matrix.cs b/ja/codes/csharp/chapter_graph/graph_adjacency_matrix.cs new file mode 100644 index 000000000..af3f0d92b --- /dev/null +++ b/ja/codes/csharp/chapter_graph/graph_adjacency_matrix.cs @@ -0,0 +1,137 @@ +/** + * File: graph_adjacency_matrix.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com) + */ + +namespace hello_algo.chapter_graph; + +/* 隣接行列に基づく無向グラフクラス */ +class GraphAdjMat { + List vertices; // 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す + List> adjMat; // 隣接行列。行・列のインデックスは「頂点インデックス」に対応 + + /* コンストラクタ */ + public GraphAdjMat(int[] vertices, int[][] edges) { + this.vertices = []; + this.adjMat = []; + // 頂点を追加 + foreach (int val in vertices) { + AddVertex(val); + } + // 辺を追加 + // 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する + foreach (int[] e in edges) { + AddEdge(e[0], e[1]); + } + } + + /* 頂点数を取得 */ + int Size() { + return vertices.Count; + } + + /* 頂点を追加 */ + public void AddVertex(int val) { + int n = Size(); + // 頂点リストに新しい頂点の値を追加 + vertices.Add(val); + // 隣接行列に 1 行追加 + List newRow = new(n); + for (int j = 0; j < n; j++) { + newRow.Add(0); + } + adjMat.Add(newRow); + // 隣接行列に 1 列追加 + foreach (List row in adjMat) { + row.Add(0); + } + } + + /* 頂点を削除 */ + public void RemoveVertex(int index) { + if (index >= Size()) + throw new IndexOutOfRangeException(); + // 頂点リストから index の頂点を削除する + vertices.RemoveAt(index); + // 隣接行列で index 行を削除する + adjMat.RemoveAt(index); + // 隣接行列で index 列を削除する + foreach (List row in adjMat) { + row.RemoveAt(index); + } + } + + /* 辺を追加 */ + // 引数 i, j は vertices の要素インデックスに対応する + public void AddEdge(int i, int j) { + // インデックスの範囲外と等値の処理 + if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) + throw new IndexOutOfRangeException(); + // 無向グラフでは、隣接行列は主対角線に関して対称、すなわち (i, j) == (j, i) を満たす + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* 辺を削除 */ + // 引数 i, j は vertices の要素インデックスに対応する + public void RemoveEdge(int i, int j) { + // インデックスの範囲外と等値の処理 + if (i < 0 || j < 0 || i >= Size() || j >= Size() || i == j) + throw new IndexOutOfRangeException(); + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* 隣接行列を出力 */ + public void Print() { + Console.Write("頂点リスト = "); + PrintUtil.PrintList(vertices); + Console.WriteLine("隣接行列 ="); + PrintUtil.PrintMatrix(adjMat); + } +} + +public class graph_adjacency_matrix { + [Test] + public void Test() { + /* 無向グラフを初期化 */ + // edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意 + int[] vertices = [1, 3, 2, 5, 4]; + int[][] edges = + [ + [0, 1], + [0, 3], + [1, 2], + [2, 3], + [2, 4], + [3, 4] + ]; + GraphAdjMat graph = new(vertices, edges); + Console.WriteLine("\n初期化後、グラフは"); + graph.Print(); + + /* 辺を追加 */ + // 頂点 1, 2 のインデックスはそれぞれ 0, 2 + graph.AddEdge(0, 2); + Console.WriteLine("\n辺 1-2 を追加した後、グラフは"); + graph.Print(); + + /* 辺を削除 */ + // 頂点 1, 3 のインデックスはそれぞれ 0, 1 + graph.RemoveEdge(0, 1); + Console.WriteLine("\n辺 1-3 を削除した後、グラフは"); + graph.Print(); + + /* 頂点を追加 */ + graph.AddVertex(6); + Console.WriteLine("\n頂点 6 を追加した後、グラフは"); + graph.Print(); + + /* 頂点を削除 */ + // 頂点 3 のインデックスは 1 + graph.RemoveVertex(1); + Console.WriteLine("\n頂点 3 を削除した後、グラフは"); + graph.Print(); + } +} diff --git a/ja/codes/csharp/chapter_graph/graph_bfs.cs b/ja/codes/csharp/chapter_graph/graph_bfs.cs new file mode 100644 index 000000000..c471fd29c --- /dev/null +++ b/ja/codes/csharp/chapter_graph/graph_bfs.cs @@ -0,0 +1,58 @@ +/** + * File: graph_bfs.cs + * Created Time: 2023-03-08 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_graph; + +public class graph_bfs { + /* 幅優先探索 */ + // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする + List GraphBFS(GraphAdjList graph, Vertex startVet) { + // 頂点の走査順序 + List res = []; + // 訪問済み頂点を記録するためのハッシュ集合 + HashSet visited = [startVet]; + // BFS の実装にキューを用いる + Queue que = new(); + que.Enqueue(startVet); + // 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す + while (que.Count > 0) { + Vertex vet = que.Dequeue(); // 先頭の頂点をデキュー + res.Add(vet); // 訪問した頂点を記録 + foreach (Vertex adjVet in graph.adjList[vet]) { + if (visited.Contains(adjVet)) { + continue; // 訪問済みの頂点をスキップ + } + que.Enqueue(adjVet); // 未訪問の頂点のみをキューに追加 + visited.Add(adjVet); // この頂点を訪問済みにする + } + } + + // 頂点の走査順を返す + return res; + } + + [Test] + public void Test() { + /* 無向グラフを初期化 */ + Vertex[] v = Vertex.ValsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + Vertex[][] edges = + [ + [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], + [v[1], v[4]], [v[2], v[5]], [v[3], v[4]], + [v[3], v[6]], [v[4], v[5]], [v[4], v[7]], + [v[5], v[8]], [v[6], v[7]], [v[7], v[8]] + ]; + + GraphAdjList graph = new(edges); + Console.WriteLine("\n初期化後、グラフは"); + graph.Print(); + + /* 幅優先探索 */ + List res = GraphBFS(graph, v[0]); + Console.WriteLine("\n幅優先探索(BFS)の頂点順序は"); + Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res))); + } +} diff --git a/ja/codes/csharp/chapter_graph/graph_dfs.cs b/ja/codes/csharp/chapter_graph/graph_dfs.cs new file mode 100644 index 000000000..eb2b52644 --- /dev/null +++ b/ja/codes/csharp/chapter_graph/graph_dfs.cs @@ -0,0 +1,54 @@ +/** + * File: graph_dfs.cs + * Created Time: 2023-03-08 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_graph; + +public class graph_dfs { + /* 深さ優先走査の補助関数 */ + void DFS(GraphAdjList graph, HashSet visited, List res, Vertex vet) { + res.Add(vet); // 訪問した頂点を記録 + visited.Add(vet); // この頂点を訪問済みにする + // この頂点のすべての隣接頂点を走査 + foreach (Vertex adjVet in graph.adjList[vet]) { + if (visited.Contains(adjVet)) { + continue; // 訪問済みの頂点をスキップ + } + // 隣接頂点を再帰的に訪問 + DFS(graph, visited, res, adjVet); + } + } + + /* 深さ優先探索 */ + // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする + List GraphDFS(GraphAdjList graph, Vertex startVet) { + // 頂点の走査順序 + List res = []; + // 訪問済み頂点を記録するためのハッシュ集合 + HashSet visited = []; + DFS(graph, visited, res, startVet); + return res; + } + + [Test] + public void Test() { + /* 無向グラフを初期化 */ + Vertex[] v = Vertex.ValsToVets([0, 1, 2, 3, 4, 5, 6]); + Vertex[][] edges = + [ + [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], + [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], + ]; + + GraphAdjList graph = new(edges); + Console.WriteLine("\n初期化後、グラフは"); + graph.Print(); + + /* 深さ優先探索 */ + List res = GraphDFS(graph, v[0]); + Console.WriteLine("\n深さ優先探索(DFS)の頂点順序は"); + Console.WriteLine(string.Join(" ", Vertex.VetsToVals(res))); + } +} diff --git a/ja/codes/csharp/chapter_greedy/coin_change_greedy.cs b/ja/codes/csharp/chapter_greedy/coin_change_greedy.cs new file mode 100644 index 000000000..6ec6c81cc --- /dev/null +++ b/ja/codes/csharp/chapter_greedy/coin_change_greedy.cs @@ -0,0 +1,54 @@ +/** +* File: coin_change_greedy.cs +* Created Time: 2023-07-21 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_greedy; + +public class coin_change_greedy { + /* コイン交換:貪欲法 */ + int CoinChangeGreedy(int[] coins, int amt) { + // coins リストはソート済みと仮定する + int i = coins.Length - 1; + int count = 0; + // 残額がなくなるまで貪欲選択を繰り返す + while (amt > 0) { + // 残額以下で最も近い硬貨を見つける + while (i > 0 && coins[i] > amt) { + i--; + } + // coins[i] を選択する + amt -= coins[i]; + count++; + } + // 実行可能な解が見つからなければ -1 を返す + return amt == 0 ? count : -1; + } + + [Test] + public void Test() { + // 貪欲法:大域最適解を保証できる + int[] coins = [1, 5, 10, 20, 50, 100]; + int amt = 186; + int res = CoinChangeGreedy(coins, amt); + Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); + Console.WriteLine("金額 " + amt + " を作るのに必要な最小硬貨枚数は " + res); + + // 貪欲法:大域最適解を保証できない + coins = [1, 20, 50]; + amt = 60; + res = CoinChangeGreedy(coins, amt); + Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); + Console.WriteLine("金額 " + amt + " を作るのに必要な最小硬貨枚数は " + res); + Console.WriteLine("実際に必要な最小枚数は 3、つまり 20 + 20 + 20"); + + // 貪欲法:大域最適解を保証できない + coins = [1, 49, 50]; + amt = 98; + res = CoinChangeGreedy(coins, amt); + Console.WriteLine("\ncoins = " + coins.PrintList() + ", amt = " + amt); + Console.WriteLine("金額 " + amt + " を作るのに必要な最小硬貨枚数は " + res); + Console.WriteLine("実際に必要な最小枚数は 2、つまり 49 + 49"); + } +} \ No newline at end of file diff --git a/ja/codes/csharp/chapter_greedy/fractional_knapsack.cs b/ja/codes/csharp/chapter_greedy/fractional_knapsack.cs new file mode 100644 index 000000000..d9774e85f --- /dev/null +++ b/ja/codes/csharp/chapter_greedy/fractional_knapsack.cs @@ -0,0 +1,52 @@ +/** +* File: fractional_knapsack.cs +* Created Time: 2023-07-21 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_greedy; + +/* 品物 */ +class Item(int w, int v) { + public int w = w; // 品物の重さ + public int v = v; // 品物の価値 +} + +public class fractional_knapsack { + /* 分数ナップサック:貪欲法 */ + double FractionalKnapsack(int[] wgt, int[] val, int cap) { + // 重さと価値の 2 属性を持つ品物リストを作成 + Item[] items = new Item[wgt.Length]; + for (int i = 0; i < wgt.Length; i++) { + items[i] = new Item(wgt[i], val[i]); + } + // 単位価値 item.v / item.w の高い順にソートする + Array.Sort(items, (x, y) => (y.v / y.w).CompareTo(x.v / x.w)); + // 貪欲選択を繰り返す + double res = 0; + foreach (Item item in items) { + if (item.w <= cap) { + // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる + res += item.v; + cap -= item.w; + } else { + // 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる + res += (double)item.v / item.w * cap; + // 残り容量がないため、ループを抜ける + break; + } + } + return res; + } + + [Test] + public void Test() { + int[] wgt = [10, 20, 30, 40, 50]; + int[] val = [50, 120, 150, 210, 240]; + int cap = 50; + + // 貪欲法 + double res = FractionalKnapsack(wgt, val, cap); + Console.WriteLine("バックパック容量を超えない最大価値は " + res); + } +} \ No newline at end of file diff --git a/ja/codes/csharp/chapter_greedy/max_capacity.cs b/ja/codes/csharp/chapter_greedy/max_capacity.cs new file mode 100644 index 000000000..1754a918e --- /dev/null +++ b/ja/codes/csharp/chapter_greedy/max_capacity.cs @@ -0,0 +1,39 @@ +/** +* File: max_capacity.cs +* Created Time: 2023-07-21 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_greedy; + +public class max_capacity { + /* 最大容量:貪欲法 */ + int MaxCapacity(int[] ht) { + // i, j を初期化し、それぞれ配列の両端に置く + int i = 0, j = ht.Length - 1; + // 初期の最大容量は 0 + int res = 0; + // 2 枚の板が出会うまで貪欲選択を繰り返す + while (i < j) { + // 最大容量を更新する + int cap = Math.Min(ht[i], ht[j]) * (j - i); + res = Math.Max(res, cap); + // 短い方を内側へ動かす + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; + } + + [Test] + public void Test() { + int[] ht = [3, 8, 5, 2, 7, 7, 3, 4]; + + // 貪欲法 + int res = MaxCapacity(ht); + Console.WriteLine("最大容量は " + res); + } +} \ No newline at end of file diff --git a/ja/codes/csharp/chapter_greedy/max_product_cutting.cs b/ja/codes/csharp/chapter_greedy/max_product_cutting.cs new file mode 100644 index 000000000..31b934e6a --- /dev/null +++ b/ja/codes/csharp/chapter_greedy/max_product_cutting.cs @@ -0,0 +1,39 @@ +/** +* File: max_product_cutting.cs +* Created Time: 2023-07-21 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_greedy; + +public class max_product_cutting { + /* 最大切断積:貪欲法 */ + int MaxProductCutting(int n) { + // n <= 3 のときは、必ず 1 を切り出す + if (n <= 3) { + return 1 * (n - 1); + } + // 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする + int a = n / 3; + int b = n % 3; + if (b == 1) { + // 余りが 1 のときは、1 * 3 を 2 * 2 に変える + return (int)Math.Pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // 余りが 2 のときは、そのままにする + return (int)Math.Pow(3, a) * 2; + } + // 余りが 0 のときは、そのままにする + return (int)Math.Pow(3, a); + } + + [Test] + public void Test() { + int n = 58; + + // 貪欲法 + int res = MaxProductCutting(n); + Console.WriteLine("最大分割積は" + res); + } +} \ No newline at end of file diff --git a/ja/codes/csharp/chapter_hashing/array_hash_map.cs b/ja/codes/csharp/chapter_hashing/array_hash_map.cs new file mode 100644 index 000000000..020a012d1 --- /dev/null +++ b/ja/codes/csharp/chapter_hashing/array_hash_map.cs @@ -0,0 +1,134 @@ +/** + * File: array_hash_map.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_hashing; + +/* キーと値の組 int->string */ +class Pair(int key, string val) { + public int key = key; + public string val = val; +} + +/* 配列ベースのハッシュテーブル */ +class ArrayHashMap { + List buckets; + public ArrayHashMap() { + // 100 個のバケットを含む配列を初期化 + buckets = []; + for (int i = 0; i < 100; i++) { + buckets.Add(null); + } + } + + /* ハッシュ関数 */ + int HashFunc(int key) { + int index = key % 100; + return index; + } + + /* 検索操作 */ + public string? Get(int key) { + int index = HashFunc(key); + Pair? pair = buckets[index]; + if (pair == null) return null; + return pair.val; + } + + /* 追加操作 */ + public void Put(int key, string val) { + Pair pair = new(key, val); + int index = HashFunc(key); + buckets[index] = pair; + } + + /* 削除操作 */ + public void Remove(int key) { + int index = HashFunc(key); + // null に設定し、削除を表す + buckets[index] = null; + } + + /* すべてのキーと値のペアを取得 */ + public List PairSet() { + List pairSet = []; + foreach (Pair? pair in buckets) { + if (pair != null) + pairSet.Add(pair); + } + return pairSet; + } + + /* すべてのキーを取得 */ + public List KeySet() { + List keySet = []; + foreach (Pair? pair in buckets) { + if (pair != null) + keySet.Add(pair.key); + } + return keySet; + } + + /* すべての値を取得 */ + public List ValueSet() { + List valueSet = []; + foreach (Pair? pair in buckets) { + if (pair != null) + valueSet.Add(pair.val); + } + return valueSet; + } + + /* ハッシュテーブルを出力 */ + public void Print() { + foreach (Pair kv in PairSet()) { + Console.WriteLine(kv.key + " -> " + kv.val); + } + } +} + + +public class array_hash_map { + [Test] + public void Test() { + /* ハッシュテーブルを初期化 */ + ArrayHashMap map = new(); + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.Put(12836, "シャオハー"); + map.Put(15937, "シャオルオ"); + map.Put(16750, "シャオスワン"); + map.Put(13276, "シャオファー"); + map.Put(10583, "シャオヤー"); + Console.WriteLine("\n追加完了後、ハッシュテーブルは\nKey -> Value"); + map.Print(); + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + string? name = map.Get(15937); + Console.WriteLine("\n学籍番号 15937 を入力すると、氏名 " + name); + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + map.Remove(10583); + Console.WriteLine("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value"); + map.Print(); + + /* ハッシュテーブルを走査 */ + Console.WriteLine("\nキーと値のペア Key->Value を走査"); + foreach (Pair kv in map.PairSet()) { + Console.WriteLine(kv.key + " -> " + kv.val); + } + Console.WriteLine("\nキー Key のみを走査"); + foreach (int key in map.KeySet()) { + Console.WriteLine(key); + } + Console.WriteLine("\n値 Value のみを走査"); + foreach (string val in map.ValueSet()) { + Console.WriteLine(val); + } + } +} diff --git a/ja/codes/csharp/chapter_hashing/built_in_hash.cs b/ja/codes/csharp/chapter_hashing/built_in_hash.cs new file mode 100644 index 000000000..f32d05d1e --- /dev/null +++ b/ja/codes/csharp/chapter_hashing/built_in_hash.cs @@ -0,0 +1,36 @@ +/** +* File: built_in_hash.cs +* Created Time: 2023-06-26 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_hashing; + +public class built_in_hash { + [Test] + public void Test() { + int num = 3; + int hashNum = num.GetHashCode(); + Console.WriteLine("整数 " + num + " のハッシュ値は " + hashNum); + + bool bol = true; + int hashBol = bol.GetHashCode(); + Console.WriteLine("真偽値 " + bol + " のハッシュ値は " + hashBol); + + double dec = 3.14159; + int hashDec = dec.GetHashCode(); + Console.WriteLine("小数 " + dec + " のハッシュ値は " + hashDec); + + string str = "Hello アルゴリズム"; + int hashStr = str.GetHashCode(); + Console.WriteLine("文字列 " + str + " のハッシュ値は " + hashStr); + + object[] arr = [12836, "シャオハー"]; + int hashTup = arr.GetHashCode(); + Console.WriteLine("配列 [" + string.Join(", ", arr) + "] のハッシュ値は " + hashTup); + + ListNode obj = new(0); + int hashObj = obj.GetHashCode(); + Console.WriteLine("ノードオブジェクト " + obj + " のハッシュ値は " + hashObj); + } +} diff --git a/ja/codes/csharp/chapter_hashing/hash_map.cs b/ja/codes/csharp/chapter_hashing/hash_map.cs new file mode 100644 index 000000000..5a3421ff9 --- /dev/null +++ b/ja/codes/csharp/chapter_hashing/hash_map.cs @@ -0,0 +1,51 @@ + +/** + * File: hash_map.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_hashing; + +public class hash_map { + [Test] + public void Test() { + /* ハッシュテーブルを初期化 */ + Dictionary map = new() { + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + { 12836, "シャオハー" }, + { 15937, "シャオルオ" }, + { 16750, "シャオスワン" }, + { 13276, "シャオファー" }, + { 10583, "シャオヤー" } + }; + Console.WriteLine("\n追加完了後、ハッシュテーブルは\nKey -> Value"); + PrintUtil.PrintHashMap(map); + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + string name = map[15937]; + Console.WriteLine("\n学籍番号 15937 を入力すると、氏名 " + name); + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + map.Remove(10583); + Console.WriteLine("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value"); + PrintUtil.PrintHashMap(map); + + /* ハッシュテーブルを走査 */ + Console.WriteLine("\nキーと値のペア Key->Value を走査"); + foreach (var kv in map) { + Console.WriteLine(kv.Key + " -> " + kv.Value); + } + Console.WriteLine("\nキー Key のみを走査"); + foreach (int key in map.Keys) { + Console.WriteLine(key); + } + Console.WriteLine("\n値 Value のみを走査"); + foreach (string val in map.Values) { + Console.WriteLine(val); + } + } +} diff --git a/ja/codes/csharp/chapter_hashing/hash_map_chaining.cs b/ja/codes/csharp/chapter_hashing/hash_map_chaining.cs new file mode 100644 index 000000000..7992d1e22 --- /dev/null +++ b/ja/codes/csharp/chapter_hashing/hash_map_chaining.cs @@ -0,0 +1,144 @@ +/** +* File: hash_map_chaining.cs +* Created Time: 2023-06-26 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_hashing; + +/* チェイン法ハッシュテーブル */ +class HashMapChaining { + int size; // キーと値のペア数 + int capacity; // ハッシュテーブル容量 + double loadThres; // リサイズを発動する負荷率のしきい値 + int extendRatio; // 拡張倍率 + List> buckets; // バケット配列 + + /* コンストラクタ */ + public HashMapChaining() { + size = 0; + capacity = 4; + loadThres = 2.0 / 3.0; + extendRatio = 2; + buckets = new List>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.Add([]); + } + } + + /* ハッシュ関数 */ + int HashFunc(int key) { + return key % capacity; + } + + /* 負荷率 */ + double LoadFactor() { + return (double)size / capacity; + } + + /* 検索操作 */ + public string? Get(int key) { + int index = HashFunc(key); + // バケットを走査し、key が見つかれば対応する val を返す + foreach (Pair pair in buckets[index]) { + if (pair.key == key) { + return pair.val; + } + } + // key が見つからない場合は null を返す + return null; + } + + /* 追加操作 */ + public void Put(int key, string val) { + // 負荷率がしきい値を超えたら、リサイズを実行 + if (LoadFactor() > loadThres) { + Extend(); + } + int index = HashFunc(key); + // バケットを走査し、指定した key が見つかれば対応する val を更新して返す + foreach (Pair pair in buckets[index]) { + if (pair.key == key) { + pair.val = val; + return; + } + } + // その key が存在しなければ、キーと値のペアを末尾に追加 + buckets[index].Add(new Pair(key, val)); + size++; + } + + /* 削除操作 */ + public void Remove(int key) { + int index = HashFunc(key); + // バケットを走査してキーと値のペアを削除 + foreach (Pair pair in buckets[index].ToList()) { + if (pair.key == key) { + buckets[index].Remove(pair); + size--; + break; + } + } + } + + /* ハッシュテーブルを拡張 */ + void Extend() { + // 元のハッシュテーブルを一時保存 + List> bucketsTmp = buckets; + // リサイズ後の新しいハッシュテーブルを初期化 + capacity *= extendRatio; + buckets = new List>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.Add([]); + } + size = 0; + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す + foreach (List bucket in bucketsTmp) { + foreach (Pair pair in bucket) { + Put(pair.key, pair.val); + } + } + } + + /* ハッシュテーブルを出力 */ + public void Print() { + foreach (List bucket in buckets) { + List res = []; + foreach (Pair pair in bucket) { + res.Add(pair.key + " -> " + pair.val); + } + foreach (string kv in res) { + Console.WriteLine(kv); + } + } + } +} + +public class hash_map_chaining { + [Test] + public void Test() { + /* ハッシュテーブルを初期化 */ + HashMapChaining map = new(); + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.Put(12836, "シャオハー"); + map.Put(15937, "シャオルオ"); + map.Put(16750, "シャオスワン"); + map.Put(13276, "シャオファー"); + map.Put(10583, "シャオヤー"); + Console.WriteLine("\n追加完了後、ハッシュテーブルは\nKey -> Value"); + map.Print(); + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + string? name = map.Get(13276); + Console.WriteLine("\n学籍番号 13276 を入力すると、氏名 " + name); + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + map.Remove(12836); + Console.WriteLine("\n12836 を削除した後、ハッシュテーブルは\nKey -> Value"); + map.Print(); + } +} \ No newline at end of file diff --git a/ja/codes/csharp/chapter_hashing/hash_map_open_addressing.cs b/ja/codes/csharp/chapter_hashing/hash_map_open_addressing.cs new file mode 100644 index 000000000..0f5a15038 --- /dev/null +++ b/ja/codes/csharp/chapter_hashing/hash_map_open_addressing.cs @@ -0,0 +1,159 @@ +/** +* File: hash_map_open_addressing.cs +* Created Time: 2023-06-26 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_hashing; + +/* オープンアドレス法ハッシュテーブル */ +class HashMapOpenAddressing { + int size; // キーと値のペア数 + int capacity = 4; // ハッシュテーブル容量 + double loadThres = 2.0 / 3.0; // リサイズを発動する負荷率のしきい値 + int extendRatio = 2; // 拡張倍率 + Pair[] buckets; // バケット配列 + Pair TOMBSTONE = new(-1, "-1"); // 削除済みマーク + + /* コンストラクタ */ + public HashMapOpenAddressing() { + size = 0; + buckets = new Pair[capacity]; + } + + /* ハッシュ関数 */ + int HashFunc(int key) { + return key % capacity; + } + + /* 負荷率 */ + double LoadFactor() { + return (double)size / capacity; + } + + /* key に対応するバケットインデックスを探す */ + int FindBucket(int key) { + int index = HashFunc(key); + int firstTombstone = -1; + // 線形プロービングを行い、空バケットに達したら終了 + while (buckets[index] != null) { + // key が見つかったら、対応するバケットのインデックスを返す + if (buckets[index].key == key) { + // 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index]; + buckets[index] = TOMBSTONE; + return firstTombstone; // 移動後のバケットインデックスを返す + } + return index; // バケットのインデックスを返す + } + // 最初に見つかった削除マークを記録 + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index; + } + // バケットのインデックスを計算し、末尾を越えたら先頭に戻る + index = (index + 1) % capacity; + } + // key が存在しない場合は追加位置のインデックスを返す + return firstTombstone == -1 ? index : firstTombstone; + } + + /* 検索操作 */ + public string? Get(int key) { + // key に対応するバケットインデックスを探す + int index = FindBucket(key); + // キーと値の組が見つかったら、対応する val を返す + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + return buckets[index].val; + } + // キーと値の組が存在しなければ null を返す + return null; + } + + /* 追加操作 */ + public void Put(int key, string val) { + // 負荷率がしきい値を超えたら、リサイズを実行 + if (LoadFactor() > loadThres) { + Extend(); + } + // key に対応するバケットインデックスを探す + int index = FindBucket(key); + // キーと値の組が見つかったら、val を上書きして返す + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index].val = val; + return; + } + // キーと値の組が存在しない場合は、その組を追加する + buckets[index] = new Pair(key, val); + size++; + } + + /* 削除操作 */ + public void Remove(int key) { + // key に対応するバケットインデックスを探す + int index = FindBucket(key); + // キーと値の組が見つかったら、削除マーカーで上書きする + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index] = TOMBSTONE; + size--; + } + } + + /* ハッシュテーブルを拡張 */ + void Extend() { + // 元のハッシュテーブルを一時保存 + Pair[] bucketsTmp = buckets; + // リサイズ後の新しいハッシュテーブルを初期化 + capacity *= extendRatio; + buckets = new Pair[capacity]; + size = 0; + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す + foreach (Pair pair in bucketsTmp) { + if (pair != null && pair != TOMBSTONE) { + Put(pair.key, pair.val); + } + } + } + + /* ハッシュテーブルを出力 */ + public void Print() { + foreach (Pair pair in buckets) { + if (pair == null) { + Console.WriteLine("null"); + } else if (pair == TOMBSTONE) { + Console.WriteLine("TOMBSTONE"); + } else { + Console.WriteLine(pair.key + " -> " + pair.val); + } + } + } +} + +public class hash_map_open_addressing { + [Test] + public void Test() { + /* ハッシュテーブルを初期化 */ + HashMapOpenAddressing map = new(); + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.Put(12836, "シャオハー"); + map.Put(15937, "シャオルオ"); + map.Put(16750, "シャオスワン"); + map.Put(13276, "シャオファー"); + map.Put(10583, "シャオヤー"); + Console.WriteLine("\n追加完了後、ハッシュテーブルは\nKey -> Value"); + map.Print(); + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + string? name = map.Get(13276); + Console.WriteLine("\n学籍番号 13276 を入力すると、氏名 " + name); + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + map.Remove(16750); + Console.WriteLine("\n16750 を削除した後、ハッシュテーブルは\nKey -> Value"); + map.Print(); + } +} diff --git a/ja/codes/csharp/chapter_hashing/simple_hash.cs b/ja/codes/csharp/chapter_hashing/simple_hash.cs new file mode 100644 index 000000000..1b209b1fa --- /dev/null +++ b/ja/codes/csharp/chapter_hashing/simple_hash.cs @@ -0,0 +1,66 @@ +/** +* File: simple_hash.cs +* Created Time: 2023-06-26 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_hashing; + +public class simple_hash { + /* 加算ハッシュ */ + int AddHash(string key) { + long hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash = (hash + c) % MODULUS; + } + return (int)hash; + } + + /* 乗算ハッシュ */ + int MulHash(string key) { + long hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash = (31 * hash + c) % MODULUS; + } + return (int)hash; + } + + /* XOR ハッシュ */ + int XorHash(string key) { + int hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash ^= c; + } + return hash & MODULUS; + } + + /* 回転ハッシュ */ + int RotHash(string key) { + long hash = 0; + const int MODULUS = 1000000007; + foreach (char c in key) { + hash = ((hash << 4) ^ (hash >> 28) ^ c) % MODULUS; + } + return (int)hash; + } + + [Test] + public void Test() { + string key = "Hello アルゴリズム"; + + int hash = AddHash(key); + Console.WriteLine("加算ハッシュ値は " + hash); + + hash = MulHash(key); + Console.WriteLine("乗算ハッシュ値は " + hash); + + hash = XorHash(key); + Console.WriteLine("XOR ハッシュ値は " + hash); + + hash = RotHash(key); + Console.WriteLine("回転ハッシュ値は " + hash); + } +} diff --git a/ja/codes/csharp/chapter_heap/heap.cs b/ja/codes/csharp/chapter_heap/heap.cs new file mode 100644 index 000000000..ca030dc64 --- /dev/null +++ b/ja/codes/csharp/chapter_heap/heap.cs @@ -0,0 +1,64 @@ +/** + * File: heap.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com) + */ + +namespace hello_algo.chapter_heap; + +public class heap { + void TestPush(PriorityQueue heap, int val) { + heap.Enqueue(val, val); // 要素をヒープに追加 + Console.WriteLine($"\n要素 {val} をヒープに追加した後\n"); + PrintUtil.PrintHeap(heap); + } + + void TestPop(PriorityQueue heap) { + int val = heap.Dequeue(); // ヒープ頂点の要素を取り出す + Console.WriteLine($"\nヒープの先頭要素 {val} を取り出した後\n"); + PrintUtil.PrintHeap(heap); + } + + [Test] + public void Test() { + /* ヒープを初期化 */ + // 最小ヒープを初期化 + PriorityQueue minHeap = new(); + // 最大ヒープを初期化する(Comparer はラムダ式で変更できる) + PriorityQueue maxHeap = new(Comparer.Create((x, y) => y.CompareTo(x))); + Console.WriteLine("以下のテストケースは最大ヒープです"); + + /* 要素をヒープに追加 */ + TestPush(maxHeap, 1); + TestPush(maxHeap, 3); + TestPush(maxHeap, 2); + TestPush(maxHeap, 5); + TestPush(maxHeap, 4); + + /* ヒープ頂点の要素を取得 */ + int peek = maxHeap.Peek(); + Console.WriteLine($"ヒープの先頭要素は {peek}"); + + /* ヒープ頂点の要素を取り出す */ + // ヒープから取り出した要素は大きい順に並ぶ + TestPop(maxHeap); + TestPop(maxHeap); + TestPop(maxHeap); + TestPop(maxHeap); + TestPop(maxHeap); + + /* ヒープのサイズを取得 */ + int size = maxHeap.Count; + Console.WriteLine($"ヒープの要素数は {size}"); + + /* ヒープが空かどうかを判定 */ + bool isEmpty = maxHeap.Count == 0; + Console.WriteLine($"ヒープは空か {isEmpty}"); + + /* リストを入力してヒープを構築 */ + var list = new int[] { 1, 3, 2, 5, 4 }; + minHeap = new PriorityQueue(list.Select(x => (x, x))); + Console.WriteLine("リストを入力して最小ヒープを構築した後"); + PrintUtil.PrintHeap(minHeap); + } +} diff --git a/ja/codes/csharp/chapter_heap/my_heap.cs b/ja/codes/csharp/chapter_heap/my_heap.cs new file mode 100644 index 000000000..610de8c7a --- /dev/null +++ b/ja/codes/csharp/chapter_heap/my_heap.cs @@ -0,0 +1,160 @@ +/** + * File: my_heap.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com) + */ + +namespace hello_algo.chapter_heap; + +/* 最大ヒープ */ +class MaxHeap { + // 配列ではなくリストを使うことで、拡張を考慮する必要がない + List maxHeap; + + /* コンストラクタ。空のヒープを作成する */ + public MaxHeap() { + maxHeap = []; + } + + /* コンストラクタ。入力リストに基づいてヒープを構築 */ + public MaxHeap(IEnumerable nums) { + // リスト要素をそのままヒープに追加 + maxHeap = new List(nums); + // 葉ノード以外のすべてのノードをヒープ化 + var size = Parent(this.Size() - 1); + for (int i = size; i >= 0; i--) { + SiftDown(i); + } + } + + /* 左子ノードのインデックスを取得 */ + int Left(int i) { + return 2 * i + 1; + } + + /* 右子ノードのインデックスを取得 */ + int Right(int i) { + return 2 * i + 2; + } + + /* 親ノードのインデックスを取得 */ + int Parent(int i) { + return (i - 1) / 2; // 切り捨て除算 + } + + /* ヒープ先頭要素にアクセス */ + public int Peek() { + return maxHeap[0]; + } + + /* 要素をヒープに追加 */ + public void Push(int val) { + // ノードを追加 + maxHeap.Add(val); + // 下から上へヒープ化 + SiftUp(Size() - 1); + } + + /* ヒープのサイズを取得 */ + public int Size() { + return maxHeap.Count; + } + + /* ヒープが空かどうかを判定 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* ノード i から始めて、下から上へヒープ化 */ + void SiftUp(int i) { + while (true) { + // ノード i の親ノードを取得 + int p = Parent(i); + // 「根ノードを越えた」または「ノードの修復が不要」な場合は、ヒープ化を終了する + if (p < 0 || maxHeap[i] <= maxHeap[p]) + break; + // 2 つのノードを交換 + Swap(i, p); + // ループで下から上へヒープ化 + i = p; + } + } + + /* 要素をヒープから取り出す */ + public int Pop() { + // 空判定の処理 + if (IsEmpty()) + throw new IndexOutOfRangeException(); + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + Swap(0, Size() - 1); + // ノードを削除 + int val = maxHeap.Last(); + maxHeap.RemoveAt(Size() - 1); + // 上から下へヒープ化 + SiftDown(0); + // ヒープ先頭要素を返す + return val; + } + + /* ノード i から始めて、上から下へヒープ化 */ + void SiftDown(int i) { + while (true) { + // ノード i, l, r のうち値が最大のノードを ma とする + int l = Left(i), r = Right(i), ma = i; + if (l < Size() && maxHeap[l] > maxHeap[ma]) + ma = l; + if (r < Size() && maxHeap[r] > maxHeap[ma]) + ma = r; + // 「ノード i が最大」または「葉ノードを越えた」場合は、ヒープ化を終了する + if (ma == i) break; + // 2 つのノードを交換 + Swap(i, ma); + // ループで上から下へヒープ化 + i = ma; + } + } + + /* 要素を交換 */ + void Swap(int i, int p) { + (maxHeap[i], maxHeap[p]) = (maxHeap[p], maxHeap[i]); + } + + /* ヒープ(二分木)を出力 */ + public void Print() { + var queue = new Queue(maxHeap); + PrintUtil.PrintHeap(queue); + } +} + +public class my_heap { + [Test] + public void Test() { + /* 最大ヒープを初期化 */ + MaxHeap maxHeap = new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + Console.WriteLine("\nリストを入力してヒープを構築した後"); + maxHeap.Print(); + + /* ヒープ頂点の要素を取得 */ + int peek = maxHeap.Peek(); + Console.WriteLine($"ヒープの先頭要素は {peek}"); + + /* 要素をヒープに追加 */ + int val = 7; + maxHeap.Push(val); + Console.WriteLine($"要素 {val} をヒープに追加した後"); + maxHeap.Print(); + + /* ヒープ頂点の要素を取り出す */ + peek = maxHeap.Pop(); + Console.WriteLine($"ヒープの先頭要素 {peek} を取り出した後"); + maxHeap.Print(); + + /* ヒープのサイズを取得 */ + int size = maxHeap.Size(); + Console.WriteLine($"ヒープの要素数は {size}"); + + /* ヒープが空かどうかを判定 */ + bool isEmpty = maxHeap.IsEmpty(); + Console.WriteLine($"ヒープは空か {isEmpty}"); + } +} diff --git a/ja/codes/csharp/chapter_heap/top_k.cs b/ja/codes/csharp/chapter_heap/top_k.cs new file mode 100644 index 000000000..04ccf9d74 --- /dev/null +++ b/ja/codes/csharp/chapter_heap/top_k.cs @@ -0,0 +1,37 @@ +/** +* File: top_k.cs +* Created Time: 2023-06-14 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_heap; + +public class top_k { + /* ヒープに基づいて配列中の最大の k 個の要素を探す */ + PriorityQueue TopKHeap(int[] nums, int k) { + // 最小ヒープを初期化 + PriorityQueue heap = new(); + // 配列の先頭 k 個の要素をヒープに追加 + for (int i = 0; i < k; i++) { + heap.Enqueue(nums[i], nums[i]); + } + // k+1 番目の要素から開始し、ヒープ長を k に保つ + for (int i = k; i < nums.Length; i++) { + // 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する + if (nums[i] > heap.Peek()) { + heap.Dequeue(); + heap.Enqueue(nums[i], nums[i]); + } + } + return heap; + } + + [Test] + public void Test() { + int[] nums = [1, 7, 6, 3, 2]; + int k = 3; + PriorityQueue res = TopKHeap(nums, k); + Console.WriteLine("最大の " + k + " 個の要素は"); + PrintUtil.PrintHeap(res); + } +} diff --git a/ja/codes/csharp/chapter_searching/binary_search.cs b/ja/codes/csharp/chapter_searching/binary_search.cs new file mode 100644 index 000000000..7a0a21109 --- /dev/null +++ b/ja/codes/csharp/chapter_searching/binary_search.cs @@ -0,0 +1,59 @@ +/** + * File: binary_search.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_searching; + +public class binary_search { + /* 二分探索(両閉区間) */ + int BinarySearch(int[] nums, int target) { + // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す + int i = 0, j = nums.Length - 1; + // ループし、探索区間が空になったら終了する(i > j で空) + while (i <= j) { + int m = i + (j - i) / 2; // 中点インデックス m を計算 + if (nums[m] < target) // この場合、target は区間 [m+1, j] にある + i = m + 1; + else if (nums[m] > target) // この場合、target は区間 [i, m-1] にある + j = m - 1; + else // 目標要素が見つかったらそのインデックスを返す + return m; + } + // 目標要素が見つからなければ -1 を返す + return -1; + } + + /* 二分探索(左閉右開区間) */ + int BinarySearchLCRO(int[] nums, int target) { + // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す + int i = 0, j = nums.Length; + // ループし、探索区間が空になったら終了する(i = j で空) + while (i < j) { + int m = i + (j - i) / 2; // 中点インデックス m を計算 + if (nums[m] < target) // この場合、target は区間 [m+1, j) にある + i = m + 1; + else if (nums[m] > target) // この場合、target は区間 [i, m) にある + j = m; + else // 目標要素が見つかったらそのインデックスを返す + return m; + } + // 目標要素が見つからなければ -1 を返す + return -1; + } + + [Test] + public void Test() { + int target = 6; + int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + /* 二分探索(両閉区間) */ + int index = BinarySearch(nums, target); + Console.WriteLine("対象要素 6 のインデックス = " + index); + + /* 二分探索(左閉右開区間) */ + index = BinarySearchLCRO(nums, target); + Console.WriteLine("対象要素 6 のインデックス = " + index); + } +} diff --git a/ja/codes/csharp/chapter_searching/binary_search_edge.cs b/ja/codes/csharp/chapter_searching/binary_search_edge.cs new file mode 100644 index 000000000..f05ddfd5d --- /dev/null +++ b/ja/codes/csharp/chapter_searching/binary_search_edge.cs @@ -0,0 +1,50 @@ +/** +* File: binary_search_edge.cs +* Created Time: 2023-08-06 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_searching; + +public class binary_search_edge { + /* 最も左の target を二分探索 */ + int BinarySearchLeftEdge(int[] nums, int target) { + // target の挿入位置を探すのと等価 + int i = binary_search_insertion.BinarySearchInsertion(nums, target); + // target が見つからなければ、-1 を返す + if (i == nums.Length || nums[i] != target) { + return -1; + } + // target が見つかったら、インデックス i を返す + return i; + } + + /* 最も右の target を二分探索 */ + int BinarySearchRightEdge(int[] nums, int target) { + // 最左の target + 1 を探す問題に変換する + int i = binary_search_insertion.BinarySearchInsertion(nums, target + 1); + // j は最も右の target を指し、i は target より大きい最初の要素を指す + int j = i - 1; + // target が見つからなければ、-1 を返す + if (j == -1 || nums[j] != target) { + return -1; + } + // target が見つかったら、インデックス j を返す + return j; + } + + [Test] + public void Test() { + // 重複要素を含む配列 + int[] nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + Console.WriteLine("\n配列 nums = " + nums.PrintList()); + + // 二分探索で左端と右端を探す + foreach (int target in new int[] { 6, 7 }) { + int index = BinarySearchLeftEdge(nums, target); + Console.WriteLine("一番左の要素 " + target + " のインデックスは " + index); + index = BinarySearchRightEdge(nums, target); + Console.WriteLine("一番右の要素 " + target + " のインデックスは " + index); + } + } +} diff --git a/ja/codes/csharp/chapter_searching/binary_search_insertion.cs b/ja/codes/csharp/chapter_searching/binary_search_insertion.cs new file mode 100644 index 000000000..78b3e9e27 --- /dev/null +++ b/ja/codes/csharp/chapter_searching/binary_search_insertion.cs @@ -0,0 +1,64 @@ +/** +* File: binary_search_insertion.cs +* Created Time: 2023-08-06 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_searching; + +public class binary_search_insertion { + /* 二分探索で挿入位置を探す(重複要素なし) */ + public static int BinarySearchInsertionSimple(int[] nums, int target) { + int i = 0, j = nums.Length - 1; // 両閉区間 [0, n-1] を初期化 + while (i <= j) { + int m = i + (j - i) / 2; // 中点インデックス m を計算 + if (nums[m] < target) { + i = m + 1; // target は区間 [m+1, j] にある + } else if (nums[m] > target) { + j = m - 1; // target は区間 [i, m-1] にある + } else { + return m; // target が見つかったら、挿入位置 m を返す + } + } + // target が見つからなければ、挿入位置 i を返す + return i; + } + + /* 二分探索で挿入位置を探す(重複要素あり) */ + public static int BinarySearchInsertion(int[] nums, int target) { + int i = 0, j = nums.Length - 1; // 両閉区間 [0, n-1] を初期化 + while (i <= j) { + int m = i + (j - i) / 2; // 中点インデックス m を計算 + if (nums[m] < target) { + i = m + 1; // target は区間 [m+1, j] にある + } else if (nums[m] > target) { + j = m - 1; // target は区間 [i, m-1] にある + } else { + j = m - 1; // target より小さい最初の要素は区間 [i, m-1] にある + } + } + // 挿入位置 i を返す + return i; + } + + [Test] + public void Test() { + // 重複要素のない配列 + int[] nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + Console.WriteLine("\n配列 nums = " + nums.PrintList()); + // 二分探索で挿入位置を探す + foreach (int target in new int[] { 6, 9 }) { + int index = BinarySearchInsertionSimple(nums, target); + Console.WriteLine("要素 " + target + " の挿入位置のインデックスは " + index); + } + + // 重複要素を含む配列 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + Console.WriteLine("\n配列 nums = " + nums.PrintList()); + // 二分探索で挿入位置を探す + foreach (int target in new int[] { 2, 6, 20 }) { + int index = BinarySearchInsertion(nums, target); + Console.WriteLine("要素 " + target + " の挿入位置のインデックスは " + index); + } + } +} diff --git a/ja/codes/csharp/chapter_searching/hashing_search.cs b/ja/codes/csharp/chapter_searching/hashing_search.cs new file mode 100644 index 000000000..9c64b0eb1 --- /dev/null +++ b/ja/codes/csharp/chapter_searching/hashing_search.cs @@ -0,0 +1,50 @@ +/** + * File: hashing_search.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_searching; + +public class hashing_search { + /* ハッシュ探索(配列) */ + int HashingSearchArray(Dictionary map, int target) { + // ハッシュテーブルの key: 目標要素、value: インデックス + // ハッシュテーブルにこの key がなければ -1 を返す + return map.GetValueOrDefault(target, -1); + } + + /* ハッシュ探索(連結リスト) */ + ListNode? HashingSearchLinkedList(Dictionary map, int target) { + + // ハッシュテーブルの key: 目標ノード値、value: ノードオブジェクト + // ハッシュテーブルにこの key がなければ null を返す + return map.GetValueOrDefault(target); + } + + [Test] + public void Test() { + int target = 3; + + /* ハッシュ探索(配列) */ + int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + // ハッシュテーブルを初期化 + Dictionary map = []; + for (int i = 0; i < nums.Length; i++) { + map[nums[i]] = i; // key: 要素、value: インデックス + } + int index = HashingSearchArray(map, target); + Console.WriteLine("対象要素 3 のインデックス = " + index); + + /* ハッシュ探索(連結リスト) */ + ListNode? head = ListNode.ArrToLinkedList(nums); + // ハッシュテーブルを初期化 + Dictionary map1 = []; + while (head != null) { + map1[head.val] = head; // key: ノード値、value: ノード + head = head.next; + } + ListNode? node = HashingSearchLinkedList(map1, target); + Console.WriteLine("目標ノード値 3 に対応するノードオブジェクトは " + node); + } +} diff --git a/ja/codes/csharp/chapter_searching/linear_search.cs b/ja/codes/csharp/chapter_searching/linear_search.cs new file mode 100644 index 000000000..a358d7a27 --- /dev/null +++ b/ja/codes/csharp/chapter_searching/linear_search.cs @@ -0,0 +1,49 @@ +/** + * File: linear_search.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_searching; + +public class linear_search { + /* 線形探索(配列) */ + int LinearSearchArray(int[] nums, int target) { + // 配列を走査 + for (int i = 0; i < nums.Length; i++) { + // 目標要素が見つかったらそのインデックスを返す + if (nums[i] == target) + return i; + } + // 目標要素が見つからなければ -1 を返す + return -1; + } + + /* 線形探索(連結リスト) */ + ListNode? LinearSearchLinkedList(ListNode? head, int target) { + // 連結リストを走査 + while (head != null) { + // 対象ノードが見つかったら、それを返す + if (head.val == target) + return head; + head = head.next; + } + // 対象ノードが見つからない場合は null を返す + return null; + } + + [Test] + public void Test() { + int target = 3; + + /* 配列で線形探索を行う */ + int[] nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + int index = LinearSearchArray(nums, target); + Console.WriteLine("対象要素 3 のインデックス = " + index); + + /* 連結リストで線形探索を行う */ + ListNode? head = ListNode.ArrToLinkedList(nums); + ListNode? node = LinearSearchLinkedList(head, target); + Console.WriteLine("目標ノード値 3 に対応するノードオブジェクトは " + node); + } +} diff --git a/ja/codes/csharp/chapter_searching/two_sum.cs b/ja/codes/csharp/chapter_searching/two_sum.cs new file mode 100644 index 000000000..79bc6be30 --- /dev/null +++ b/ja/codes/csharp/chapter_searching/two_sum.cs @@ -0,0 +1,52 @@ +/** + * File: two_sum.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_searching; + +public class two_sum { + /* 方法 1:総当たり列挙 */ + int[] TwoSumBruteForce(int[] nums, int target) { + int size = nums.Length; + // 2重ループのため、時間計算量は O(n^2) + for (int i = 0; i < size - 1; i++) { + for (int j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) + return [i, j]; + } + } + return []; + } + + /* 方法 2:補助ハッシュテーブル */ + int[] TwoSumHashTable(int[] nums, int target) { + int size = nums.Length; + // 補助ハッシュテーブルを使用し、空間計算量は O(n) + Dictionary dic = []; + // 単一ループで、時間計算量は O(n) + for (int i = 0; i < size; i++) { + if (dic.ContainsKey(target - nums[i])) { + return [dic[target - nums[i]], i]; + } + dic.Add(nums[i], i); + } + return []; + } + + [Test] + public void Test() { + // ======= Test Case ======= + int[] nums = [2, 7, 11, 15]; + int target = 13; + + // ====== Driver Code ====== + // 方法 1 + int[] res = TwoSumBruteForce(nums, target); + Console.WriteLine("方法1 res = " + string.Join(",", res)); + // 方法 2 + res = TwoSumHashTable(nums, target); + Console.WriteLine("方法2 res = " + string.Join(",", res)); + } +} diff --git a/ja/codes/csharp/chapter_sorting/bubble_sort.cs b/ja/codes/csharp/chapter_sorting/bubble_sort.cs new file mode 100644 index 000000000..b434da1d0 --- /dev/null +++ b/ja/codes/csharp/chapter_sorting/bubble_sort.cs @@ -0,0 +1,51 @@ +/** + * File: bubble_sort.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_sorting; + +public class bubble_sort { + /* バブルソート */ + void BubbleSort(int[] nums) { + // 外側のループ:未ソート区間は [0, i] + for (int i = nums.Length - 1; i > 0; i--) { + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // nums[j] と nums[j + 1] を交換 + (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); + } + } + } + } + + /* バブルソート(フラグ最適化) */ + void BubbleSortWithFlag(int[] nums) { + // 外側のループ:未ソート区間は [0, i] + for (int i = nums.Length - 1; i > 0; i--) { + bool flag = false; // フラグを初期化する + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // nums[j] と nums[j + 1] を交換 + (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]); + flag = true; // 交換する要素を記録 + } + } + if (!flag) break; // このバブル処理で要素交換が一度もなければそのまま終了 + } + } + + [Test] + public void Test() { + int[] nums = [4, 1, 3, 1, 5, 2]; + BubbleSort(nums); + Console.WriteLine("バブルソート完了後 nums = " + string.Join(",", nums)); + + int[] nums1 = [4, 1, 3, 1, 5, 2]; + BubbleSortWithFlag(nums1); + Console.WriteLine("バブルソート完了後 nums1 = " + string.Join(",", nums1)); + } +} diff --git a/ja/codes/csharp/chapter_sorting/bucket_sort.cs b/ja/codes/csharp/chapter_sorting/bucket_sort.cs new file mode 100644 index 000000000..8ec92ba2d --- /dev/null +++ b/ja/codes/csharp/chapter_sorting/bucket_sort.cs @@ -0,0 +1,46 @@ +/** + * File: bucket_sort.cs + * Created Time: 2023-04-13 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_sorting; + +public class bucket_sort { + /* バケットソート */ + void BucketSort(float[] nums) { + // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする + int k = nums.Length / 2; + List> buckets = []; + for (int i = 0; i < k; i++) { + buckets.Add([]); + } + // 1. 配列要素を各バケットに振り分ける + foreach (float num in nums) { + // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する + int i = (int)(num * k); + // num をバケット i に追加 + buckets[i].Add(num); + } + // 2. 各バケットをソートする + foreach (List bucket in buckets) { + // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい + bucket.Sort(); + } + // 3. バケットを走査して結果を結合 + int j = 0; + foreach (List bucket in buckets) { + foreach (float num in bucket) { + nums[j++] = num; + } + } + } + + [Test] + public void Test() { + // 入力データは範囲 [0, 1) の浮動小数点数とする + float[] nums = [0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f]; + BucketSort(nums); + Console.WriteLine("バケットソート完了後 nums = " + string.Join(" ", nums)); + } +} diff --git a/ja/codes/csharp/chapter_sorting/counting_sort.cs b/ja/codes/csharp/chapter_sorting/counting_sort.cs new file mode 100644 index 000000000..5bb821566 --- /dev/null +++ b/ja/codes/csharp/chapter_sorting/counting_sort.cs @@ -0,0 +1,77 @@ +/** + * File: counting_sort.cs + * Created Time: 2023-04-13 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_sorting; + +public class counting_sort { + /* 計数ソート */ + // 簡易実装のため、オブジェクトのソートには使えない + void CountingSortNaive(int[] nums) { + // 1. 配列の最大要素 m を求める + int m = 0; + foreach (int num in nums) { + m = Math.Max(m, num); + } + // 2. 各数値の出現回数を数える + // counter[num] は num の出現回数を表す + int[] counter = new int[m + 1]; + foreach (int num in nums) { + counter[num]++; + } + // 3. counter を走査し、各要素を元の配列 nums に書き戻す + int i = 0; + for (int num = 0; num < m + 1; num++) { + for (int j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } + } + + /* 計数ソート */ + // 完全な実装で、オブジェクトをソートでき、かつ安定ソートである + void CountingSort(int[] nums) { + // 1. 配列の最大要素 m を求める + int m = 0; + foreach (int num in nums) { + m = Math.Max(m, num); + } + // 2. 各数値の出現回数を数える + // counter[num] は num の出現回数を表す + int[] counter = new int[m + 1]; + foreach (int num in nums) { + counter[num]++; + } + // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する + // つまり counter[num]-1 は、num が res に最後に現れるインデックス + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. nums を逆順に走査し、各要素を結果配列 res に格納する + // 結果を記録するための配列 res を初期化 + int n = nums.Length; + int[] res = new int[n]; + for (int i = n - 1; i >= 0; i--) { + int num = nums[i]; + res[counter[num] - 1] = num; // num を対応するインデックスに配置 + counter[num]--; // 累積和を 1 減らして、次に num を配置するインデックスを得る + } + // 結果配列 res で元の配列 nums を上書きする + for (int i = 0; i < n; i++) { + nums[i] = res[i]; + } + } + + [Test] + public void Test() { + int[] nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + CountingSortNaive(nums); + Console.WriteLine("カウントソート(オブジェクトはソート不可)完了後 nums = " + string.Join(" ", nums)); + + int[] nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + CountingSort(nums1); + Console.WriteLine("カウントソート完了後 nums1 = " + string.Join(" ", nums)); + } +} diff --git a/ja/codes/csharp/chapter_sorting/heap_sort.cs b/ja/codes/csharp/chapter_sorting/heap_sort.cs new file mode 100644 index 000000000..b9d002352 --- /dev/null +++ b/ja/codes/csharp/chapter_sorting/heap_sort.cs @@ -0,0 +1,52 @@ +/** +* File: heap_sort.cs +* Created Time: 2023-06-01 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_sorting; + +public class heap_sort { + /* ヒープの長さは n。ノード i から下方向にヒープ化 */ + void SiftDown(int[] nums, int n, int i) { + while (true) { + // ノード i, l, r のうち値が最大のノードを ma とする + int l = 2 * i + 1; + int r = 2 * i + 2; + int ma = i; + if (l < n && nums[l] > nums[ma]) + ma = l; + if (r < n && nums[r] > nums[ma]) + ma = r; + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + if (ma == i) + break; + // 2 つのノードを交換 + (nums[ma], nums[i]) = (nums[i], nums[ma]); + // ループで上から下へヒープ化 + i = ma; + } + } + + /* ヒープソート */ + void HeapSort(int[] nums) { + // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する + for (int i = nums.Length / 2 - 1; i >= 0; i--) { + SiftDown(nums, nums.Length, i); + } + // ヒープから最大要素を取り出し、n-1 回繰り返す + for (int i = nums.Length - 1; i > 0; i--) { + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + (nums[i], nums[0]) = (nums[0], nums[i]); + // 根ノードを起点に、上から下へヒープ化 + SiftDown(nums, i, 0); + } + } + + [Test] + public void Test() { + int[] nums = [4, 1, 3, 1, 5, 2]; + HeapSort(nums); + Console.WriteLine("ヒープソート完了後 nums = " + string.Join(" ", nums)); + } +} diff --git a/ja/codes/csharp/chapter_sorting/insertion_sort.cs b/ja/codes/csharp/chapter_sorting/insertion_sort.cs new file mode 100644 index 000000000..2a5b932ed --- /dev/null +++ b/ja/codes/csharp/chapter_sorting/insertion_sort.cs @@ -0,0 +1,30 @@ +/** + * File: insertion_sort.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_sorting; + +public class insertion_sort { + /* 挿入ソート */ + void InsertionSort(int[] nums) { + // 外側ループ:整列済み区間は [0, i-1] + for (int i = 1; i < nums.Length; i++) { + int bas = nums[i], j = i - 1; + // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する + while (j >= 0 && nums[j] > bas) { + nums[j + 1] = nums[j]; // nums[j] を 1 つ右へ移動する + j--; + } + nums[j + 1] = bas; // base を正しい位置に配置する + } + } + + [Test] + public void Test() { + int[] nums = [4, 1, 3, 1, 5, 2]; + InsertionSort(nums); + Console.WriteLine("挿入ソート完了後 nums = " + string.Join(",", nums)); + } +} diff --git a/ja/codes/csharp/chapter_sorting/merge_sort.cs b/ja/codes/csharp/chapter_sorting/merge_sort.cs new file mode 100644 index 000000000..67b776ee6 --- /dev/null +++ b/ja/codes/csharp/chapter_sorting/merge_sort.cs @@ -0,0 +1,56 @@ +/** + * File: merge_sort.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_sorting; + +public class merge_sort { + /* 左部分配列と右部分配列をマージ */ + void Merge(int[] nums, int left, int mid, int right) { + // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] + // マージ結果を格納する一時配列 tmp を作成 + int[] tmp = new int[right - left + 1]; + // 左右の部分配列の開始インデックスを初期化する + int i = left, j = mid + 1, k = 0; + // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) + tmp[k++] = nums[i++]; + else + tmp[k++] = nums[j++]; + } + // 左右の部分配列の残り要素を一時配列にコピーする + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする + for (k = 0; k < tmp.Length; ++k) { + nums[left + k] = tmp[k]; + } + } + + /* マージソート */ + void MergeSort(int[] nums, int left, int right) { + // 終了条件 + if (left >= right) return; // 部分配列の長さが 1 になったら再帰を終了 + // 分割フェーズ + int mid = left + (right - left) / 2; // 中点を計算 + MergeSort(nums, left, mid); // 左部分配列を再帰処理 + MergeSort(nums, mid + 1, right); // 右部分配列を再帰処理 + // マージフェーズ + Merge(nums, left, mid, right); + } + + [Test] + public void Test() { + /* マージソート */ + int[] nums = [7, 3, 2, 6, 0, 1, 5, 4]; + MergeSort(nums, 0, nums.Length - 1); + Console.WriteLine("マージソート完了後 nums = " + string.Join(",", nums)); + } +} diff --git a/ja/codes/csharp/chapter_sorting/quick_sort.cs b/ja/codes/csharp/chapter_sorting/quick_sort.cs new file mode 100644 index 000000000..6f09454fd --- /dev/null +++ b/ja/codes/csharp/chapter_sorting/quick_sort.cs @@ -0,0 +1,150 @@ +/** + * File: quick_sort.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_sorting; + +class quickSort { + /* 要素の交換 */ + static void Swap(int[] nums, int i, int j) { + (nums[j], nums[i]) = (nums[i], nums[j]); + } + + /* 番兵分割 */ + static int Partition(int[] nums, int left, int right) { + // nums[left] を基準値とする + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // 右から左へ基準値未満の最初の要素を探す + while (i < j && nums[i] <= nums[left]) + i++; // 左から右へ基準値より大きい最初の要素を探す + Swap(nums, i, j); // この 2 つの要素を交換 + } + Swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する + return i; // 基準値のインデックスを返す + } + + /* クイックソート */ + public static void QuickSort(int[] nums, int left, int right) { + // 部分配列の長さが 1 なら再帰を終了する + if (left >= right) + return; + // 番兵分割 + int pivot = Partition(nums, left, right); + // 左右の部分配列を再帰処理 + QuickSort(nums, left, pivot - 1); + QuickSort(nums, pivot + 1, right); + } +} + +/* クイックソートクラス(中央値ピボット最適化) */ +class QuickSortMedian { + /* 要素の交換 */ + static void Swap(int[] nums, int i, int j) { + (nums[j], nums[i]) = (nums[i], nums[j]); + } + + /* 3つの候補要素の中央値を選ぶ */ + static int MedianThree(int[] nums, int left, int mid, int right) { + int l = nums[left], m = nums[mid], r = nums[right]; + if ((l <= m && m <= r) || (r <= m && m <= l)) + return mid; // m は l と r の間 + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // l は m と r の間 + return right; + } + + /* 番兵による分割処理(3 点中央値) */ + static int Partition(int[] nums, int left, int right) { + // 3つの候補要素の中央値を選ぶ + int med = MedianThree(nums, left, (left + right) / 2, right); + // 中央値を配列の最左端に交換する + Swap(nums, left, med); + // nums[left] を基準値とする + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // 右から左へ基準値未満の最初の要素を探す + while (i < j && nums[i] <= nums[left]) + i++; // 左から右へ基準値より大きい最初の要素を探す + Swap(nums, i, j); // この 2 つの要素を交換 + } + Swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する + return i; // 基準値のインデックスを返す + } + + /* クイックソート */ + public static void QuickSort(int[] nums, int left, int right) { + // 部分配列の長さが 1 なら再帰を終了する + if (left >= right) + return; + // 番兵分割 + int pivot = Partition(nums, left, right); + // 左右の部分配列を再帰処理 + QuickSort(nums, left, pivot - 1); + QuickSort(nums, pivot + 1, right); + } +} + +/* クイックソートクラス(再帰深度最適化) */ +class QuickSortTailCall { + /* 要素の交換 */ + static void Swap(int[] nums, int i, int j) { + (nums[j], nums[i]) = (nums[i], nums[j]); + } + + /* 番兵分割 */ + static int Partition(int[] nums, int left, int right) { + // nums[left] を基準値とする + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j--; // 右から左へ基準値未満の最初の要素を探す + while (i < j && nums[i] <= nums[left]) + i++; // 左から右へ基準値より大きい最初の要素を探す + Swap(nums, i, j); // この 2 つの要素を交換 + } + Swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する + return i; // 基準値のインデックスを返す + } + + /* クイックソート(再帰深度最適化) */ + public static void QuickSort(int[] nums, int left, int right) { + // 部分配列の長さが 1 なら終了 + while (left < right) { + // 番兵による分割処理 + int pivot = Partition(nums, left, right); + // 2 つの部分配列のうち短いほうにクイックソートを適用する + if (pivot - left < right - pivot) { + QuickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソート + left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right] + } else { + QuickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソート + right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1] + } + } + } +} + +public class quick_sort { + [Test] + public void Test() { + /* クイックソート */ + int[] nums = [2, 4, 1, 0, 3, 5]; + quickSort.QuickSort(nums, 0, nums.Length - 1); + Console.WriteLine("クイックソート完了後 nums = " + string.Join(",", nums)); + + /* クイックソート(中央値の基準値で最適化) */ + int[] nums1 = [2, 4, 1, 0, 3, 5]; + QuickSortMedian.QuickSort(nums1, 0, nums1.Length - 1); + Console.WriteLine("クイックソート(中央値ピボット最適化)完了後 nums1 = " + string.Join(",", nums1)); + + /* クイックソート(再帰深度最適化) */ + int[] nums2 = [2, 4, 1, 0, 3, 5]; + QuickSortTailCall.QuickSort(nums2, 0, nums2.Length - 1); + Console.WriteLine("クイックソート(再帰深度最適化)完了後 nums2 = " + string.Join(",", nums2)); + } +} diff --git a/ja/codes/csharp/chapter_sorting/radix_sort.cs b/ja/codes/csharp/chapter_sorting/radix_sort.cs new file mode 100644 index 000000000..c1a30b0e5 --- /dev/null +++ b/ja/codes/csharp/chapter_sorting/radix_sort.cs @@ -0,0 +1,69 @@ +/** + * File: radix_sort.cs + * Created Time: 2023-04-13 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_sorting; + +public class radix_sort { + /* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */ + int Digit(int num, int exp) { + // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す + return (num / exp) % 10; + } + + /* 計数ソート(nums の k 桁目でソート) */ + void CountingSortDigit(int[] nums, int exp) { + // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 + int[] counter = new int[10]; + int n = nums.Length; + // 0~9 の各数字の出現回数を集計する + for (int i = 0; i < n; i++) { + int d = Digit(nums[i], exp); // nums[i] の第 k 位を取得し、d とする + counter[d]++; // 数字 d の出現回数を数える + } + // 累積和を求め、「出現回数」を「配列インデックス」に変換する + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する + int[] res = new int[n]; + for (int i = n - 1; i >= 0; i--) { + int d = Digit(nums[i], exp); + int j = counter[d] - 1; // d の配列内インデックス j を取得する + res[j] = nums[i]; // 現在の要素をインデックス j に格納する + counter[d]--; // d の個数を 1 減らす + } + // 結果で元の配列 nums を上書きする + for (int i = 0; i < n; i++) { + nums[i] = res[i]; + } + } + + /* 基数ソート */ + void RadixSort(int[] nums) { + // 最大桁数の判定用に配列の最大要素を取得 + int m = int.MinValue; + foreach (int num in nums) { + if (num > m) m = num; + } + // 下位桁から上位桁の順に走査する + for (int exp = 1; exp <= m; exp *= 10) { + // 配列要素の k 桁目に対して計数ソートを行う + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // つまり exp = 10^(k-1) + CountingSortDigit(nums, exp); + } + } + + [Test] + public void Test() { + // 基数ソート + int[] nums = [ 10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996 ]; + RadixSort(nums); + Console.WriteLine("基数ソート完了後 nums = " + string.Join(" ", nums)); + } +} diff --git a/ja/codes/csharp/chapter_sorting/selection_sort.cs b/ja/codes/csharp/chapter_sorting/selection_sort.cs new file mode 100644 index 000000000..dfbe020e8 --- /dev/null +++ b/ja/codes/csharp/chapter_sorting/selection_sort.cs @@ -0,0 +1,32 @@ +/** +* File: selection_sort.cs +* Created Time: 2023-06-01 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_sorting; + +public class selection_sort { + /* 選択ソート */ + void SelectionSort(int[] nums) { + int n = nums.Length; + // 外側ループ:未整列区間は [i, n-1] + for (int i = 0; i < n - 1; i++) { + // 内側のループ:未ソート区間の最小要素を見つける + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) + k = j; // 最小要素のインデックスを記録 + } + // その最小要素を未整列区間の先頭要素と交換する + (nums[k], nums[i]) = (nums[i], nums[k]); + } + } + + [Test] + public void Test() { + int[] nums = [4, 1, 3, 1, 5, 2]; + SelectionSort(nums); + Console.WriteLine("選択ソート完了後 nums = " + string.Join(" ", nums)); + } +} diff --git a/ja/codes/csharp/chapter_stack_and_queue/array_deque.cs b/ja/codes/csharp/chapter_stack_and_queue/array_deque.cs new file mode 100644 index 000000000..483ae89ee --- /dev/null +++ b/ja/codes/csharp/chapter_stack_and_queue/array_deque.cs @@ -0,0 +1,152 @@ +/** + * File: array_deque.cs + * Created Time: 2023-03-08 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* 循環配列ベースの両端キュー */ +public class ArrayDeque { + int[] nums; // 両端キューの要素を格納する配列 + int front; // 先頭ポインタ。先頭要素を指す + int queSize; // 両端キューの長さ + + /* コンストラクタ */ + public ArrayDeque(int capacity) { + nums = new int[capacity]; + front = queSize = 0; + } + + /* 両端キューの容量を取得 */ + int Capacity() { + return nums.Length; + } + + /* 両端キューの長さを取得 */ + public int Size() { + return queSize; + } + + /* 両端キューが空かどうかを判定 */ + public bool IsEmpty() { + return queSize == 0; + } + + /* 循環配列のインデックスを計算 */ + int Index(int i) { + // 剰余演算により配列の先頭と末尾をつなげる + // i が配列の末尾を越えたら先頭に戻る + // i が配列の先頭を越えて前に出たら末尾に戻る + return (i + Capacity()) % Capacity(); + } + + /* キュー先頭にエンキュー */ + public void PushFirst(int num) { + if (queSize == Capacity()) { + Console.WriteLine("両端キューは満杯です"); + return; + } + // 先頭ポインタを左に 1 つ移動する + // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする + front = Index(front - 1); + // num をキュー先頭に追加 + nums[front] = num; + queSize++; + } + + /* キュー末尾にエンキュー */ + public void PushLast(int num) { + if (queSize == Capacity()) { + Console.WriteLine("両端キューは満杯です"); + return; + } + // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す + int rear = Index(front + queSize); + // num をキュー末尾に追加 + nums[rear] = num; + queSize++; + } + + /* キュー先頭からデキュー */ + public int PopFirst() { + int num = PeekFirst(); + // 先頭ポインタを 1 つ後ろへ進める + front = Index(front + 1); + queSize--; + return num; + } + + /* キュー末尾からデキュー */ + public int PopLast() { + int num = PeekLast(); + queSize--; + return num; + } + + /* キュー先頭の要素にアクセス */ + public int PeekFirst() { + if (IsEmpty()) { + throw new InvalidOperationException(); + } + return nums[front]; + } + + /* キュー末尾の要素にアクセス */ + public int PeekLast() { + if (IsEmpty()) { + throw new InvalidOperationException(); + } + // 末尾要素のインデックスを計算 + int last = Index(front + queSize - 1); + return nums[last]; + } + + /* 出力用の配列を返す */ + public int[] ToArray() { + // 有効長の範囲内のリスト要素のみを変換 + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[Index(j)]; + } + return res; + } +} + +public class array_deque { + [Test] + public void Test() { + /* 両端キューを初期化 */ + ArrayDeque deque = new(10); + deque.PushLast(3); + deque.PushLast(2); + deque.PushLast(5); + Console.WriteLine("双方向キュー deque = " + string.Join(" ", deque.ToArray())); + + /* 要素にアクセス */ + int peekFirst = deque.PeekFirst(); + Console.WriteLine("先頭要素 peekFirst = " + peekFirst); + int peekLast = deque.PeekLast(); + Console.WriteLine("末尾要素 peekLast = " + peekLast); + + /* 要素をエンキュー */ + deque.PushLast(4); + Console.WriteLine("要素 4 を末尾にエンキューした後 deque = " + string.Join(" ", deque.ToArray())); + deque.PushFirst(1); + Console.WriteLine("要素 1 を先頭にエンキューした後 deque = " + string.Join(" ", deque.ToArray())); + + /* 要素をデキュー */ + int popLast = deque.PopLast(); + Console.WriteLine("末尾からデキューした要素 = " + popLast + "、末尾からデキューした後 deque = " + string.Join(" ", deque.ToArray())); + int popFirst = deque.PopFirst(); + Console.WriteLine("先頭からデキューした要素 = " + popFirst + "、先頭からデキューした後 deque = " + string.Join(" ", deque.ToArray())); + + /* 両端キューの長さを取得 */ + int size = deque.Size(); + Console.WriteLine("双方向キューの長さ size = " + size); + + /* 両端キューが空かどうかを判定 */ + bool isEmpty = deque.IsEmpty(); + Console.WriteLine("双方向キューが空かどうか = " + isEmpty); + } +} diff --git a/ja/codes/csharp/chapter_stack_and_queue/array_queue.cs b/ja/codes/csharp/chapter_stack_and_queue/array_queue.cs new file mode 100644 index 000000000..bed7ccc71 --- /dev/null +++ b/ja/codes/csharp/chapter_stack_and_queue/array_queue.cs @@ -0,0 +1,114 @@ +/** + * File: array_queue.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* 循環配列ベースのキュー */ +class ArrayQueue { + int[] nums; // キュー要素を格納する配列 + int front; // 先頭ポインタ。先頭要素を指す + int queSize; // キューの長さ + + public ArrayQueue(int capacity) { + nums = new int[capacity]; + front = queSize = 0; + } + + /* キューの容量を取得 */ + int Capacity() { + return nums.Length; + } + + /* キューの長さを取得 */ + public int Size() { + return queSize; + } + + /* キューが空かどうかを判定 */ + public bool IsEmpty() { + return queSize == 0; + } + + /* エンキュー */ + public void Push(int num) { + if (queSize == Capacity()) { + Console.WriteLine("キューは満杯です"); + return; + } + // 末尾ポインタを計算し、末尾インデックス + 1 を指す + // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする + int rear = (front + queSize) % Capacity(); + // num をキュー末尾に追加 + nums[rear] = num; + queSize++; + } + + /* デキュー */ + public int Pop() { + int num = Peek(); + // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す + front = (front + 1) % Capacity(); + queSize--; + return num; + } + + /* キュー先頭の要素にアクセス */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return nums[front]; + } + + /* 配列を返す */ + public int[] ToArray() { + // 有効長の範囲内のリスト要素のみを変換 + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[j % this.Capacity()]; + } + return res; + } +} + +public class array_queue { + [Test] + public void Test() { + /* キューを初期化 */ + int capacity = 10; + ArrayQueue queue = new(capacity); + + /* 要素をエンキュー */ + queue.Push(1); + queue.Push(3); + queue.Push(2); + queue.Push(5); + queue.Push(4); + Console.WriteLine("キュー queue = " + string.Join(",", queue.ToArray())); + + /* キュー先頭の要素にアクセス */ + int peek = queue.Peek(); + Console.WriteLine("先頭要素 peek = " + peek); + + /* 要素をデキュー */ + int pop = queue.Pop(); + Console.WriteLine("デキューした要素 pop = " + pop + "、デキュー後の queue = " + string.Join(",", queue.ToArray())); + + /* キューの長さを取得 */ + int size = queue.Size(); + Console.WriteLine("キューの長さ size = " + size); + + /* キューが空かどうかを判定 */ + bool isEmpty = queue.IsEmpty(); + Console.WriteLine("キューが空かどうか = " + isEmpty); + + /* 循環配列をテストする */ + for (int i = 0; i < 10; i++) { + queue.Push(i); + queue.Pop(); + Console.WriteLine("第 " + i + " 回のエンキュー + デキュー後 queue = " + string.Join(",", queue.ToArray())); + } + } +} diff --git a/ja/codes/csharp/chapter_stack_and_queue/array_stack.cs b/ja/codes/csharp/chapter_stack_and_queue/array_stack.cs new file mode 100644 index 000000000..19bd30010 --- /dev/null +++ b/ja/codes/csharp/chapter_stack_and_queue/array_stack.cs @@ -0,0 +1,84 @@ +/** + * File: array_stack.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* 配列ベースのスタック */ +class ArrayStack { + List stack; + public ArrayStack() { + // リスト(動的配列)を初期化する + stack = []; + } + + /* スタックの長さを取得 */ + public int Size() { + return stack.Count; + } + + /* スタックが空かどうかを判定 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* プッシュ */ + public void Push(int num) { + stack.Add(num); + } + + /* ポップ */ + public int Pop() { + if (IsEmpty()) + throw new Exception(); + var val = Peek(); + stack.RemoveAt(Size() - 1); + return val; + } + + /* スタックトップの要素にアクセス */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return stack[Size() - 1]; + } + + /* List を Array に変換して返す */ + public int[] ToArray() { + return [.. stack]; + } +} + +public class array_stack { + [Test] + public void Test() { + /* スタックを初期化 */ + ArrayStack stack = new(); + + /* 要素をプッシュ */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + Console.WriteLine("スタック stack = " + string.Join(",", stack.ToArray())); + + /* スタックトップの要素にアクセス */ + int peek = stack.Peek(); + Console.WriteLine("スタックトップ要素 peek = " + peek); + + /* 要素をポップ */ + int pop = stack.Pop(); + Console.WriteLine("ポップした要素 pop = " + pop + "、ポップ後の stack = " + string.Join(",", stack.ToArray())); + + /* スタックの長さを取得 */ + int size = stack.Size(); + Console.WriteLine("スタックの長さ size = " + size); + + /* 空かどうかを判定 */ + bool isEmpty = stack.IsEmpty(); + Console.WriteLine("スタックが空かどうか = " + isEmpty); + } +} diff --git a/ja/codes/csharp/chapter_stack_and_queue/deque.cs b/ja/codes/csharp/chapter_stack_and_queue/deque.cs new file mode 100644 index 000000000..d6ac7bff5 --- /dev/null +++ b/ja/codes/csharp/chapter_stack_and_queue/deque.cs @@ -0,0 +1,44 @@ +/** + * File: deque.cs + * Created Time: 2022-12-30 + * Author: moonache (microin1301@outlook.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +public class deque { + [Test] + public void Test() { + /* 両端キューを初期化 */ + // C# では、LinkedList を両端キューとして使う + LinkedList deque = new(); + + /* 要素をエンキュー */ + deque.AddLast(2); // 末尾に追加する + deque.AddLast(5); + deque.AddLast(4); + deque.AddFirst(3); // 先頭に追加する + deque.AddFirst(1); + Console.WriteLine("双方向キュー deque = " + string.Join(",", deque)); + + /* 要素にアクセス */ + int? peekFirst = deque.First?.Value; // 先頭要素 + Console.WriteLine("先頭要素 peekFirst = " + peekFirst); + int? peekLast = deque.Last?.Value; // 末尾要素 + Console.WriteLine("末尾要素 peekLast = " + peekLast); + + /* 要素をデキュー */ + deque.RemoveFirst(); // 先頭要素を取り出す + Console.WriteLine("先頭要素をデキューした後 deque = " + string.Join(",", deque)); + deque.RemoveLast(); // 末尾要素を取り出す + Console.WriteLine("末尾要素をデキューした後 deque = " + string.Join(",", deque)); + + /* 両端キューの長さを取得 */ + int size = deque.Count; + Console.WriteLine("双方向キューの長さ size = " + size); + + /* 両端キューが空かどうかを判定 */ + bool isEmpty = deque.Count == 0; + Console.WriteLine("双方向キューが空かどうか = " + isEmpty); + } +} diff --git a/ja/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs b/ja/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs new file mode 100644 index 000000000..628ae85a3 --- /dev/null +++ b/ja/codes/csharp/chapter_stack_and_queue/linkedlist_deque.cs @@ -0,0 +1,177 @@ +/** + * File: linkedlist_deque.cs + * Created Time: 2023-03-08 + * Author: hpstory (hpstory1024@163.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* 双方向連結リストノード */ +public class ListNode(int val) { + public int val = val; // ノード値 + public ListNode? next = null; // 後続ノードへの参照 + public ListNode? prev = null; // 前駆ノードへの参照 +} + +/* 双方向連結リストベースの両端キュー */ +public class LinkedListDeque { + ListNode? front, rear; // 先頭ノード front、末尾ノード rear + int queSize = 0; // 両端キューの長さ + + public LinkedListDeque() { + front = null; + rear = null; + } + + /* 両端キューの長さを取得 */ + public int Size() { + return queSize; + } + + /* 両端キューが空かどうかを判定 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* エンキュー操作 */ + void Push(int num, bool isFront) { + ListNode node = new(num); + // 連結リストが空なら、front と rear の両方を node に向ける + if (IsEmpty()) { + front = node; + rear = node; + } + // 先頭へのエンキュー操作 + else if (isFront) { + // node を連結リストの先頭に追加 + front!.prev = node; + node.next = front; + front = node; // 先頭ノードを更新する + } + // 末尾へのエンキュー操作 + else { + // node を連結リストの末尾に追加 + rear!.next = node; + node.prev = rear; + rear = node; // 末尾ノードを更新する + } + + queSize++; // キューの長さを更新 + } + + /* キュー先頭にエンキュー */ + public void PushFirst(int num) { + Push(num, true); + } + + /* キュー末尾にエンキュー */ + public void PushLast(int num) { + Push(num, false); + } + + /* デキュー操作 */ + int? Pop(bool isFront) { + if (IsEmpty()) + throw new Exception(); + int? val; + // キュー先頭からの取り出し + if (isFront) { + val = front?.val; // 先頭ノードの値を一時保存 + // 先頭ノードを削除 + ListNode? fNext = front?.next; + if (fNext != null) { + fNext.prev = null; + front!.next = null; + } + front = fNext; // 先頭ノードを更新する + } + // キュー末尾からの取り出し + else { + val = rear?.val; // 末尾ノードの値を一時保存 + // 末尾ノードを削除 + ListNode? rPrev = rear?.prev; + if (rPrev != null) { + rPrev.next = null; + rear!.prev = null; + } + rear = rPrev; // 末尾ノードを更新する + } + + queSize--; // キューの長さを更新 + return val; + } + + /* キュー先頭からデキュー */ + public int? PopFirst() { + return Pop(true); + } + + /* キュー末尾からデキュー */ + public int? PopLast() { + return Pop(false); + } + + /* キュー先頭の要素にアクセス */ + public int? PeekFirst() { + if (IsEmpty()) + throw new Exception(); + return front?.val; + } + + /* キュー末尾の要素にアクセス */ + public int? PeekLast() { + if (IsEmpty()) + throw new Exception(); + return rear?.val; + } + + /* 出力用の配列を返す */ + public int?[] ToArray() { + ListNode? node = front; + int?[] res = new int?[Size()]; + for (int i = 0; i < res.Length; i++) { + res[i] = node?.val; + node = node?.next; + } + + return res; + } +} + +public class linkedlist_deque { + [Test] + public void Test() { + /* 両端キューを初期化 */ + LinkedListDeque deque = new(); + deque.PushLast(3); + deque.PushLast(2); + deque.PushLast(5); + Console.WriteLine("双方向キュー deque = " + string.Join(" ", deque.ToArray())); + + /* 要素にアクセス */ + int? peekFirst = deque.PeekFirst(); + Console.WriteLine("先頭要素 peekFirst = " + peekFirst); + int? peekLast = deque.PeekLast(); + Console.WriteLine("末尾要素 peekLast = " + peekLast); + + /* 要素をエンキュー */ + deque.PushLast(4); + Console.WriteLine("要素 4 を末尾にエンキューした後 deque = " + string.Join(" ", deque.ToArray())); + deque.PushFirst(1); + Console.WriteLine("要素 1 を先頭にエンキューした後 deque = " + string.Join(" ", deque.ToArray())); + + /* 要素をデキュー */ + int? popLast = deque.PopLast(); + Console.WriteLine("末尾からデキューした要素 = " + popLast + "、末尾からデキューした後 deque = " + string.Join(" ", deque.ToArray())); + int? popFirst = deque.PopFirst(); + Console.WriteLine("先頭からデキューした要素 = " + popFirst + "、先頭からデキューした後 deque = " + string.Join(" ", deque.ToArray())); + + /* 両端キューの長さを取得 */ + int size = deque.Size(); + Console.WriteLine("双方向キューの長さ size = " + size); + + /* 両端キューが空かどうかを判定 */ + bool isEmpty = deque.IsEmpty(); + Console.WriteLine("双方向キューが空かどうか = " + isEmpty); + } +} diff --git a/ja/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs b/ja/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs new file mode 100644 index 000000000..e9198f5a3 --- /dev/null +++ b/ja/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs @@ -0,0 +1,106 @@ +/** + * File: linkedlist_queue.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* 連結リストベースのキュー */ +class LinkedListQueue { + ListNode? front, rear; // 先頭ノード front、末尾ノード rear + int queSize = 0; + + public LinkedListQueue() { + front = null; + rear = null; + } + + /* キューの長さを取得 */ + public int Size() { + return queSize; + } + + /* キューが空かどうかを判定 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* エンキュー */ + public void Push(int num) { + // 末尾ノードの後ろに num を追加 + ListNode node = new(num); + // キューが空なら、先頭・末尾ノードをともにそのノードに設定 + if (front == null) { + front = node; + rear = node; + // キューが空でなければ、そのノードを末尾ノードの後ろに追加 + } else if (rear != null) { + rear.next = node; + rear = node; + } + queSize++; + } + + /* デキュー */ + public int Pop() { + int num = Peek(); + // 先頭ノードを削除 + front = front?.next; + queSize--; + return num; + } + + /* キュー先頭の要素にアクセス */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return front!.val; + } + + /* 連結リストを Array に変換して返す */ + public int[] ToArray() { + if (front == null) + return []; + + ListNode? node = front; + int[] res = new int[Size()]; + for (int i = 0; i < res.Length; i++) { + res[i] = node!.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_queue { + [Test] + public void Test() { + /* キューを初期化 */ + LinkedListQueue queue = new(); + + /* 要素をエンキュー */ + queue.Push(1); + queue.Push(3); + queue.Push(2); + queue.Push(5); + queue.Push(4); + Console.WriteLine("キュー queue = " + string.Join(",", queue.ToArray())); + + /* キュー先頭の要素にアクセス */ + int peek = queue.Peek(); + Console.WriteLine("先頭要素 peek = " + peek); + + /* 要素をデキュー */ + int pop = queue.Pop(); + Console.WriteLine("デキューした要素 pop = " + pop + "、デキュー後の queue = " + string.Join(",", queue.ToArray())); + + /* キューの長さを取得 */ + int size = queue.Size(); + Console.WriteLine("キューの長さ size = " + size); + + /* キューが空かどうかを判定 */ + bool isEmpty = queue.IsEmpty(); + Console.WriteLine("キューが空かどうか = " + isEmpty); + } +} diff --git a/ja/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs b/ja/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs new file mode 100644 index 000000000..7a0eec6e5 --- /dev/null +++ b/ja/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs @@ -0,0 +1,97 @@ +/** + * File: linkedlist_stack.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +/* 連結リストベースのスタック */ +class LinkedListStack { + ListNode? stackPeek; // 先頭ノードをスタックトップとする + int stkSize = 0; // スタックの長さ + + public LinkedListStack() { + stackPeek = null; + } + + /* スタックの長さを取得 */ + public int Size() { + return stkSize; + } + + /* スタックが空かどうかを判定 */ + public bool IsEmpty() { + return Size() == 0; + } + + /* プッシュ */ + public void Push(int num) { + ListNode node = new(num) { + next = stackPeek + }; + stackPeek = node; + stkSize++; + } + + /* ポップ */ + public int Pop() { + int num = Peek(); + stackPeek = stackPeek!.next; + stkSize--; + return num; + } + + /* スタックトップの要素にアクセス */ + public int Peek() { + if (IsEmpty()) + throw new Exception(); + return stackPeek!.val; + } + + /* List を Array に変換して返す */ + public int[] ToArray() { + if (stackPeek == null) + return []; + + ListNode? node = stackPeek; + int[] res = new int[Size()]; + for (int i = res.Length - 1; i >= 0; i--) { + res[i] = node!.val; + node = node.next; + } + return res; + } +} + +public class linkedlist_stack { + [Test] + public void Test() { + /* スタックを初期化 */ + LinkedListStack stack = new(); + + /* 要素をプッシュ */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + Console.WriteLine("スタック stack = " + string.Join(",", stack.ToArray())); + + /* スタックトップの要素にアクセス */ + int peek = stack.Peek(); + Console.WriteLine("スタックトップ要素 peek = " + peek); + + /* 要素をポップ */ + int pop = stack.Pop(); + Console.WriteLine("ポップした要素 pop = " + pop + "、ポップ後の stack = " + string.Join(",", stack.ToArray())); + + /* スタックの長さを取得 */ + int size = stack.Size(); + Console.WriteLine("スタックの長さ size = " + size); + + /* 空かどうかを判定 */ + bool isEmpty = stack.IsEmpty(); + Console.WriteLine("スタックが空かどうか = " + isEmpty); + } +} diff --git a/ja/codes/csharp/chapter_stack_and_queue/queue.cs b/ja/codes/csharp/chapter_stack_and_queue/queue.cs new file mode 100644 index 000000000..af0585211 --- /dev/null +++ b/ja/codes/csharp/chapter_stack_and_queue/queue.cs @@ -0,0 +1,39 @@ +/** + * File: queue.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +public class queue { + [Test] + public void Test() { + /* キューを初期化 */ + Queue queue = new(); + + /* 要素をエンキュー */ + queue.Enqueue(1); + queue.Enqueue(3); + queue.Enqueue(2); + queue.Enqueue(5); + queue.Enqueue(4); + Console.WriteLine("キュー queue = " + string.Join(",", queue)); + + /* キュー先頭の要素にアクセス */ + int peek = queue.Peek(); + Console.WriteLine("先頭要素 peek = " + peek); + + /* 要素をデキュー */ + int pop = queue.Dequeue(); + Console.WriteLine("デキューした要素 pop = " + pop + "、デキュー後の queue = " + string.Join(",", queue)); + + /* キューの長さを取得 */ + int size = queue.Count; + Console.WriteLine("キューの長さ size = " + size); + + /* キューが空かどうかを判定 */ + bool isEmpty = queue.Count == 0; + Console.WriteLine("キューが空かどうか = " + isEmpty); + } +} diff --git a/ja/codes/csharp/chapter_stack_and_queue/stack.cs b/ja/codes/csharp/chapter_stack_and_queue/stack.cs new file mode 100644 index 000000000..117e477ec --- /dev/null +++ b/ja/codes/csharp/chapter_stack_and_queue/stack.cs @@ -0,0 +1,40 @@ +/** + * File: stack.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_stack_and_queue; + +public class stack { + [Test] + public void Test() { + /* スタックを初期化 */ + Stack stack = new(); + + /* 要素をプッシュ */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + // 注意:stack.ToArray() で得られるのは逆順のシーケンスであり、インデックス 0 がスタックトップです + Console.WriteLine("スタック stack = " + string.Join(",", stack)); + + /* スタックトップの要素にアクセス */ + int peek = stack.Peek(); + Console.WriteLine("スタックトップ要素 peek = " + peek); + + /* 要素をポップ */ + int pop = stack.Pop(); + Console.WriteLine("ポップした要素 pop = " + pop + "、ポップ後の stack = " + string.Join(",", stack)); + + /* スタックの長さを取得 */ + int size = stack.Count; + Console.WriteLine("スタックの長さ size = " + size); + + /* 空かどうかを判定 */ + bool isEmpty = stack.Count == 0; + Console.WriteLine("スタックが空かどうか = " + isEmpty); + } +} diff --git a/ja/codes/csharp/chapter_tree/array_binary_tree.cs b/ja/codes/csharp/chapter_tree/array_binary_tree.cs new file mode 100644 index 000000000..95b8e54ed --- /dev/null +++ b/ja/codes/csharp/chapter_tree/array_binary_tree.cs @@ -0,0 +1,129 @@ +/** +* File: array_binary_tree.cs +* Created Time: 2023-07-20 +* Author: hpstory (hpstory1024@163.com) +*/ + +namespace hello_algo.chapter_tree; + +/* 配列表現による二分木クラス */ +public class ArrayBinaryTree(List arr) { + List tree = new(arr); + + /* リスト容量 */ + public int Size() { + return tree.Count; + } + + /* インデックス i のノードの値を取得 */ + public int? Val(int i) { + // インデックスが範囲外なら、空きを表す null を返す + if (i < 0 || i >= Size()) + return null; + return tree[i]; + } + + /* インデックス i のノードの左子ノードのインデックスを取得 */ + public int Left(int i) { + return 2 * i + 1; + } + + /* インデックス i のノードの右子ノードのインデックスを取得 */ + public int Right(int i) { + return 2 * i + 2; + } + + /* インデックス i のノードの親ノードのインデックスを取得 */ + public int Parent(int i) { + return (i - 1) / 2; + } + + /* レベル順走査 */ + public List LevelOrder() { + List res = []; + // 配列を直接走査する + for (int i = 0; i < Size(); i++) { + if (Val(i).HasValue) + res.Add(Val(i)!.Value); + } + return res; + } + + /* 深さ優先探索 */ + void DFS(int i, string order, List res) { + // 空きスロットなら返す + if (!Val(i).HasValue) + return; + // 先行順走査 + if (order == "pre") + res.Add(Val(i)!.Value); + DFS(Left(i), order, res); + // 中順走査 + if (order == "in") + res.Add(Val(i)!.Value); + DFS(Right(i), order, res); + // 後順走査 + if (order == "post") + res.Add(Val(i)!.Value); + } + + /* 先行順走査 */ + public List PreOrder() { + List res = []; + DFS(0, "pre", res); + return res; + } + + /* 中順走査 */ + public List InOrder() { + List res = []; + DFS(0, "in", res); + return res; + } + + /* 後順走査 */ + public List PostOrder() { + List res = []; + DFS(0, "post", res); + return res; + } +} + +public class array_binary_tree { + [Test] + public void Test() { + // 二分木を初期化 + // ここでは、配列から直接二分木を生成する関数を利用する + List arr = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; + + TreeNode? root = TreeNode.ListToTree(arr); + Console.WriteLine("\n二分木を初期化\n"); + Console.WriteLine("二分木の配列表現:"); + Console.WriteLine(arr.PrintList()); + Console.WriteLine("二分木のリンクドリスト表現:"); + PrintUtil.PrintTree(root); + + // 配列表現による二分木クラス + ArrayBinaryTree abt = new(arr); + + // ノードにアクセス + int i = 1; + int l = abt.Left(i); + int r = abt.Right(i); + int p = abt.Parent(i); + Console.WriteLine("\n現在のノードのインデックスは " + i + " 、値は " + abt.Val(i)); + Console.WriteLine("左子ノードのインデックスは " + l + " 、値は " + (abt.Val(l).HasValue ? abt.Val(l) : "null")); + Console.WriteLine("右子ノードのインデックスは " + r + " 、値は " + (abt.Val(r).HasValue ? abt.Val(r) : "null")); + Console.WriteLine("親ノードのインデックスは " + p + " 、値は " + (abt.Val(p).HasValue ? abt.Val(p) : "null")); + + // 木を走査 + List res = abt.LevelOrder(); + Console.WriteLine("\nレベル順走査:" + res.PrintList()); + res = abt.PreOrder(); + Console.WriteLine("前順走査:" + res.PrintList()); + res = abt.InOrder(); + Console.WriteLine("中順走査:" + res.PrintList()); + res = abt.PostOrder(); + Console.WriteLine("後順走査:" + res.PrintList()); + } +} \ No newline at end of file diff --git a/ja/codes/csharp/chapter_tree/avl_tree.cs b/ja/codes/csharp/chapter_tree/avl_tree.cs new file mode 100644 index 000000000..688a7e399 --- /dev/null +++ b/ja/codes/csharp/chapter_tree/avl_tree.cs @@ -0,0 +1,216 @@ +/** + * File: avl_tree.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_tree; + +/* AVL 木 */ +class AVLTree { + public TreeNode? root; // 根ノード + + /* ノードの高さを取得 */ + int Height(TreeNode? node) { + // 空ノードの高さは -1、葉ノードの高さは 0 + return node == null ? -1 : node.height; + } + + /* ノードの高さを更新する */ + void UpdateHeight(TreeNode node) { + // ノードの高さは最も高い部分木の高さ + 1 に等しい + node.height = Math.Max(Height(node.left), Height(node.right)) + 1; + } + + /* 平衡係数を取得 */ + public int BalanceFactor(TreeNode? node) { + // 空ノードの平衡係数は 0 + if (node == null) return 0; + // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ + return Height(node.left) - Height(node.right); + } + + /* 右回転 */ + TreeNode? RightRotate(TreeNode? node) { + TreeNode? child = node?.left; + TreeNode? grandChild = child?.right; + // child を支点として node を右回転させる + child.right = node; + node.left = grandChild; + // ノードの高さを更新する + UpdateHeight(node); + UpdateHeight(child); + // 回転後の部分木の根ノードを返す + return child; + } + + /* 左回転 */ + TreeNode? LeftRotate(TreeNode? node) { + TreeNode? child = node?.right; + TreeNode? grandChild = child?.left; + // child を支点として node を左回転させる + child.left = node; + node.right = grandChild; + // ノードの高さを更新する + UpdateHeight(node); + UpdateHeight(child); + // 回転後の部分木の根ノードを返す + return child; + } + + /* 回転操作を行い、この部分木の平衡を回復する */ + TreeNode? Rotate(TreeNode? node) { + // ノード node の平衡係数を取得 + int balanceFactorInt = BalanceFactor(node); + // 左に偏った木 + if (balanceFactorInt > 1) { + if (BalanceFactor(node?.left) >= 0) { + // 右回転 + return RightRotate(node); + } else { + // 左回転してから右回転 + node!.left = LeftRotate(node!.left); + return RightRotate(node); + } + } + // 右に偏った木 + if (balanceFactorInt < -1) { + if (BalanceFactor(node?.right) <= 0) { + // 左回転 + return LeftRotate(node); + } else { + // 右回転してから左回転 + node!.right = RightRotate(node!.right); + return LeftRotate(node); + } + } + // 平衡木なので回転不要、そのまま返す + return node; + } + + /* ノードを挿入 */ + public void Insert(int val) { + root = InsertHelper(root, val); + } + + /* ノードを再帰的に挿入する(補助メソッド) */ + TreeNode? InsertHelper(TreeNode? node, int val) { + if (node == null) return new TreeNode(val); + /* 1. 挿入位置を探索してノードを挿入 */ + if (val < node.val) + node.left = InsertHelper(node.left, val); + else if (val > node.val) + node.right = InsertHelper(node.right, val); + else + return node; // 重複ノードは挿入せず、そのまま返す + UpdateHeight(node); // ノードの高さを更新する + /* 2. 回転操作を行い、部分木の平衡を回復する */ + node = Rotate(node); + // 部分木の根ノードを返す + return node; + } + + /* ノードを削除 */ + public void Remove(int val) { + root = RemoveHelper(root, val); + } + + /* ノードを再帰的に削除する(補助メソッド) */ + TreeNode? RemoveHelper(TreeNode? node, int val) { + if (node == null) return null; + /* 1. ノードを探索して削除 */ + if (val < node.val) + node.left = RemoveHelper(node.left, val); + else if (val > node.val) + node.right = RemoveHelper(node.right, val); + else { + if (node.left == null || node.right == null) { + TreeNode? child = node.left ?? node.right; + // 子ノード数 = 0 の場合、node をそのまま削除して返す + if (child == null) + return null; + // 子ノード数 = 1 の場合、node をそのまま削除する + else + node = child; + } else { + // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える + TreeNode? temp = node.right; + while (temp.left != null) { + temp = temp.left; + } + node.right = RemoveHelper(node.right, temp.val!.Value); + node.val = temp.val; + } + } + UpdateHeight(node); // ノードの高さを更新する + /* 2. 回転操作を行い、部分木の平衡を回復する */ + node = Rotate(node); + // 部分木の根ノードを返す + return node; + } + + /* ノードを探索 */ + public TreeNode? Search(int val) { + TreeNode? cur = root; + // ループで探索し、葉ノードを越えたら抜ける + while (cur != null) { + // 目標ノードは cur の右部分木にある + if (cur.val < val) + cur = cur.right; + // 目標ノードは cur の左部分木にある + else if (cur.val > val) + cur = cur.left; + // 目標ノードが見つかったらループを抜ける + else + break; + } + // 目標ノードを返す + return cur; + } +} + +public class avl_tree { + static void TestInsert(AVLTree tree, int val) { + tree.Insert(val); + Console.WriteLine("\nノード " + val + " を挿入した後の AVL 木は"); + PrintUtil.PrintTree(tree.root); + } + + static void TestRemove(AVLTree tree, int val) { + tree.Remove(val); + Console.WriteLine("\nノード " + val + " を削除した後、AVL 木は"); + PrintUtil.PrintTree(tree.root); + } + + [Test] + public void Test() { + /* 空の AVL 木を初期化する */ + AVLTree avlTree = new(); + + /* ノードを挿入 */ + // ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい + TestInsert(avlTree, 1); + TestInsert(avlTree, 2); + TestInsert(avlTree, 3); + TestInsert(avlTree, 4); + TestInsert(avlTree, 5); + TestInsert(avlTree, 8); + TestInsert(avlTree, 7); + TestInsert(avlTree, 9); + TestInsert(avlTree, 10); + TestInsert(avlTree, 6); + + /* 重複ノードを挿入する */ + TestInsert(avlTree, 7); + + /* ノードを削除 */ + // ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい + TestRemove(avlTree, 8); // 次数 0 のノードを削除する + TestRemove(avlTree, 5); // 次数 1 のノードを削除する + TestRemove(avlTree, 4); // 次数 2 のノードを削除する + + /* ノードを検索 */ + TreeNode? node = avlTree.Search(7); + Console.WriteLine("\n見つかったノードオブジェクトは " + node + "、ノード値 = " + node?.val); + } +} diff --git a/ja/codes/csharp/chapter_tree/binary_search_tree.cs b/ja/codes/csharp/chapter_tree/binary_search_tree.cs new file mode 100644 index 000000000..4227b029b --- /dev/null +++ b/ja/codes/csharp/chapter_tree/binary_search_tree.cs @@ -0,0 +1,160 @@ +/** + * File: binary_search_tree.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_tree; + +class BinarySearchTree { + TreeNode? root; + + public BinarySearchTree() { + // 空の木を初期化する + root = null; + } + + /* 二分木の根ノードを取得 */ + public TreeNode? GetRoot() { + return root; + } + + /* ノードを探索 */ + public TreeNode? Search(int num) { + TreeNode? cur = root; + // ループで探索し、葉ノードを越えたら抜ける + while (cur != null) { + // 目標ノードは cur の右部分木にある + if (cur.val < num) cur = + cur.right; + // 目標ノードは cur の左部分木にある + else if (cur.val > num) + cur = cur.left; + // 目標ノードが見つかったらループを抜ける + else + break; + } + // 目標ノードを返す + return cur; + } + + /* ノードを挿入 */ + public void Insert(int num) { + // 木が空なら、根ノードを初期化する + if (root == null) { + root = new TreeNode(num); + return; + } + TreeNode? cur = root, pre = null; + // ループで探索し、葉ノードを越えたら抜ける + while (cur != null) { + // 重複ノードが見つかったら、直ちに返す + if (cur.val == num) + return; + pre = cur; + // 挿入位置は cur の右部分木にある + if (cur.val < num) + cur = cur.right; + // 挿入位置は cur の左部分木にある + else + cur = cur.left; + } + + // ノードを挿入 + TreeNode node = new(num); + if (pre != null) { + if (pre.val < num) + pre.right = node; + else + pre.left = node; + } + } + + + /* ノードを削除 */ + public void Remove(int num) { + // 木が空なら、そのまま早期リターンする + if (root == null) + return; + TreeNode? cur = root, pre = null; + // ループで探索し、葉ノードを越えたら抜ける + while (cur != null) { + // 削除対象のノードが見つかったら、ループを抜ける + if (cur.val == num) + break; + pre = cur; + // 削除対象ノードは cur の右部分木にある + if (cur.val < num) + cur = cur.right; + // 削除対象ノードは cur の左部分木にある + else + cur = cur.left; + } + // 削除対象ノードがなければそのまま返す + if (cur == null) + return; + // 子ノード数 = 0 or 1 + if (cur.left == null || cur.right == null) { + // 子ノード数が 0 / 1 のとき、child = null / その子ノード + TreeNode? child = cur.left ?? cur.right; + // ノード cur を削除する + if (cur != root) { + if (pre!.left == cur) + pre.left = child; + else + pre.right = child; + } else { + // 削除ノードが根ノードなら、根ノードを再設定 + root = child; + } + } + // 子ノード数 = 2 + else { + // 中順走査における cur の次ノードを取得 + TreeNode? tmp = cur.right; + while (tmp.left != null) { + tmp = tmp.left; + } + // ノード tmp を再帰的に削除 + Remove(tmp.val!.Value); + // tmp で cur を上書きする + cur.val = tmp.val; + } + } +} + +public class binary_search_tree { + [Test] + public void Test() { + /* 二分探索木を初期化 */ + BinarySearchTree bst = new(); + // 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる + int[] nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; + foreach (int num in nums) { + bst.Insert(num); + } + + Console.WriteLine("\n初期化した二分木は\n"); + PrintUtil.PrintTree(bst.GetRoot()); + + /* ノードを探索 */ + TreeNode? node = bst.Search(7); + Console.WriteLine("\n見つかったノードオブジェクトは " + node + "、ノード値 = " + node?.val); + + /* ノードを挿入 */ + bst.Insert(16); + Console.WriteLine("\nノード 16 を挿入した後、二分木は\n"); + PrintUtil.PrintTree(bst.GetRoot()); + + /* ノードを削除 */ + bst.Remove(1); + Console.WriteLine("\nノード 1 を削除した後、二分木は\n"); + PrintUtil.PrintTree(bst.GetRoot()); + bst.Remove(2); + Console.WriteLine("\nノード 2 を削除した後、二分木は\n"); + PrintUtil.PrintTree(bst.GetRoot()); + bst.Remove(4); + Console.WriteLine("\nノード 4 を削除した後、二分木は\n"); + PrintUtil.PrintTree(bst.GetRoot()); + } +} diff --git a/ja/codes/csharp/chapter_tree/binary_tree.cs b/ja/codes/csharp/chapter_tree/binary_tree.cs new file mode 100644 index 000000000..497f7baae --- /dev/null +++ b/ja/codes/csharp/chapter_tree/binary_tree.cs @@ -0,0 +1,39 @@ +/** + * File: binary_tree.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_tree; + +public class binary_tree { + [Test] + public void Test() { + /* 二分木を初期化 */ + // ノードを初期化 + TreeNode n1 = new(1); + TreeNode n2 = new(2); + TreeNode n3 = new(3); + TreeNode n4 = new(4); + TreeNode n5 = new(5); + // ノード間の参照(ポインタ)を構築する + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + Console.WriteLine("\n二分木を初期化\n"); + PrintUtil.PrintTree(n1); + + /* ノードの挿入と削除 */ + TreeNode P = new(0); + // n1 -> n2 の間にノード P を挿入 + n1.left = P; + P.left = n2; + Console.WriteLine("\nノード P を挿入した後\n"); + PrintUtil.PrintTree(n1); + // ノード P を削除 + n1.left = n2; + Console.WriteLine("\nノード P を削除した後\n"); + PrintUtil.PrintTree(n1); + } +} diff --git a/ja/codes/csharp/chapter_tree/binary_tree_bfs.cs b/ja/codes/csharp/chapter_tree/binary_tree_bfs.cs new file mode 100644 index 000000000..0f378e9c6 --- /dev/null +++ b/ja/codes/csharp/chapter_tree/binary_tree_bfs.cs @@ -0,0 +1,40 @@ +/** + * File: binary_tree_bfs.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_tree; + +public class binary_tree_bfs { + + /* レベル順走査 */ + List LevelOrder(TreeNode root) { + // キューを初期化し、ルートノードを追加する + Queue queue = new(); + queue.Enqueue(root); + // 走査順序を保存するためのリストを初期化する + List list = []; + while (queue.Count != 0) { + TreeNode node = queue.Dequeue(); // デキュー + list.Add(node.val!.Value); // ノードの値を保存する + if (node.left != null) + queue.Enqueue(node.left); // 左子ノードをキューに追加 + if (node.right != null) + queue.Enqueue(node.right); // 右子ノードをキューに追加 + } + return list; + } + + [Test] + public void Test() { + /* 二分木を初期化 */ + // ここでは、配列から直接二分木を生成する関数を利用する + TreeNode? root = TreeNode.ListToTree([1, 2, 3, 4, 5, 6, 7]); + Console.WriteLine("\n二分木を初期化\n"); + PrintUtil.PrintTree(root); + + List list = LevelOrder(root!); + Console.WriteLine("\nレベル順走査のノード出力シーケンス = " + string.Join(",", list)); + } +} diff --git a/ja/codes/csharp/chapter_tree/binary_tree_dfs.cs b/ja/codes/csharp/chapter_tree/binary_tree_dfs.cs new file mode 100644 index 000000000..74518cb07 --- /dev/null +++ b/ja/codes/csharp/chapter_tree/binary_tree_dfs.cs @@ -0,0 +1,59 @@ +/** + * File: binary_tree_dfs.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.chapter_tree; + +public class binary_tree_dfs { + List list = []; + + /* 先行順走査 */ + void PreOrder(TreeNode? root) { + if (root == null) return; + // 訪問順序:根ノード -> 左部分木 -> 右部分木 + list.Add(root.val!.Value); + PreOrder(root.left); + PreOrder(root.right); + } + + /* 中順走査 */ + void InOrder(TreeNode? root) { + if (root == null) return; + // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 + InOrder(root.left); + list.Add(root.val!.Value); + InOrder(root.right); + } + + /* 後順走査 */ + void PostOrder(TreeNode? root) { + if (root == null) return; + // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード + PostOrder(root.left); + PostOrder(root.right); + list.Add(root.val!.Value); + } + + [Test] + public void Test() { + /* 二分木を初期化 */ + // ここでは、配列から直接二分木を生成する関数を利用する + TreeNode? root = TreeNode.ListToTree([1, 2, 3, 4, 5, 6, 7]); + Console.WriteLine("\n二分木を初期化\n"); + PrintUtil.PrintTree(root); + + list.Clear(); + PreOrder(root); + Console.WriteLine("\n前順走査のノード出力シーケンス = " + string.Join(",", list)); + + list.Clear(); + InOrder(root); + Console.WriteLine("\n中順走査のノード出力シーケンス = " + string.Join(",", list)); + + list.Clear(); + PostOrder(root); + Console.WriteLine("\n後順走査のノード出力シーケンス = " + string.Join(",", list)); + } +} diff --git a/ja/codes/csharp/csharp.sln b/ja/codes/csharp/csharp.sln new file mode 100644 index 000000000..0c74ccc53 --- /dev/null +++ b/ja/codes/csharp/csharp.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "hello-algo", "hello-algo.csproj", "{48B60439-EFDC-4C8F-AE8D-41979958C8AC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48B60439-EFDC-4C8F-AE8D-41979958C8AC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1E773F8A-FF66-4974-820B-FCE9032D19AE} + EndGlobalSection +EndGlobal diff --git a/ja/codes/csharp/hello-algo.csproj b/ja/codes/csharp/hello-algo.csproj new file mode 100644 index 000000000..43817cc38 --- /dev/null +++ b/ja/codes/csharp/hello-algo.csproj @@ -0,0 +1,21 @@ + + + + Exe + net8.0 + hello_algo + enable + enable + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/ja/codes/csharp/utils/ListNode.cs b/ja/codes/csharp/utils/ListNode.cs new file mode 100644 index 000000000..8800370c2 --- /dev/null +++ b/ja/codes/csharp/utils/ListNode.cs @@ -0,0 +1,32 @@ +// File: ListNode.cs +// Created Time: 2022-12-16 +// Author: mingXta (1195669834@qq.com) + +namespace hello_algo.utils; + +/* 連結リストノード */ +public class ListNode(int x) { + public int val = x; + public ListNode? next; + + /* 配列をデシリアライズして連結リストに変換する */ + public static ListNode? ArrToLinkedList(int[] arr) { + ListNode dum = new(0); + ListNode head = dum; + foreach (int val in arr) { + head.next = new ListNode(val); + head = head.next; + } + return dum.next; + } + + public override string? ToString() { + List list = []; + var head = this; + while (head != null) { + list.Add(head.val.ToString()); + head = head.next; + } + return string.Join("->", list); + } +} diff --git a/ja/codes/csharp/utils/PrintUtil.cs b/ja/codes/csharp/utils/PrintUtil.cs new file mode 100644 index 000000000..f63ea275c --- /dev/null +++ b/ja/codes/csharp/utils/PrintUtil.cs @@ -0,0 +1,132 @@ +/** +* File: PrintUtil.cs +* Created Time: 2022-12-23 +* Author: haptear (haptear@hotmail.com), krahets (krahets@163.com) +*/ + +namespace hello_algo.utils; + +public class Trunk(Trunk? prev, string str) { + public Trunk? prev = prev; + public string str = str; +}; + +public static class PrintUtil { + /* リストを出力する */ + public static void PrintList(IList list) { + Console.WriteLine("[" + string.Join(", ", list) + "]"); + } + + public static string PrintList(this IEnumerable list) { + return $"[ {string.Join(", ", list.Select(x => x?.ToString() ?? "null"))} ]"; + } + + /* 行列を出力する (Array) */ + public static void PrintMatrix(T[][] matrix) { + Console.WriteLine("["); + foreach (T[] row in matrix) { + Console.WriteLine(" " + string.Join(", ", row) + ","); + } + Console.WriteLine("]"); + } + + /* 行列を出力 (List) */ + public static void PrintMatrix(List> matrix) { + Console.WriteLine("["); + foreach (List row in matrix) { + Console.WriteLine(" " + string.Join(", ", row) + ","); + } + Console.WriteLine("]"); + } + + /* 連結リストを出力 */ + public static void PrintLinkedList(ListNode? head) { + List list = []; + while (head != null) { + list.Add(head.val.ToString()); + head = head.next; + } + Console.Write(string.Join(" -> ", list)); + } + + /** + * 二分木を出力 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ + public static void PrintTree(TreeNode? root) { + PrintTree(root, null, false); + } + + /* 二分木を出力 */ + public static void PrintTree(TreeNode? root, Trunk? prev, bool isRight) { + if (root == null) { + return; + } + + string prev_str = " "; + Trunk trunk = new(prev, prev_str); + + PrintTree(root.right, trunk, true); + + if (prev == null) { + trunk.str = "———"; + } else if (isRight) { + trunk.str = "/———"; + prev_str = " |"; + } else { + trunk.str = "\\———"; + prev.str = prev_str; + } + + ShowTrunks(trunk); + Console.WriteLine(" " + root.val); + + if (prev != null) { + prev.str = prev_str; + } + trunk.str = " |"; + + PrintTree(root.left, trunk, false); + } + + public static void ShowTrunks(Trunk? p) { + if (p == null) { + return; + } + + ShowTrunks(p.prev); + Console.Write(p.str); + } + + /* ハッシュテーブルを出力 */ + public static void PrintHashMap(Dictionary map) where K : notnull { + foreach (var kv in map.Keys) { + Console.WriteLine(kv.ToString() + " -> " + map[kv]?.ToString()); + } + } + + /* ヒープを出力 */ + public static void PrintHeap(Queue queue) { + Console.Write("ヒープの配列表現:"); + List list = [.. queue]; + Console.WriteLine(string.Join(',', list)); + Console.WriteLine("ヒープの木構造表示:"); + TreeNode? tree = TreeNode.ListToTree(list.Cast().ToList()); + PrintTree(tree); + } + + /* 優先キューを出力 */ + public static void PrintHeap(PriorityQueue queue) { + var newQueue = new PriorityQueue(queue.UnorderedItems, queue.Comparer); + Console.Write("ヒープの配列表現:"); + List list = []; + while (newQueue.TryDequeue(out int element, out _)) { + list.Add(element); + } + Console.WriteLine("ヒープの木構造表示:"); + Console.WriteLine(string.Join(',', list.ToList())); + TreeNode? tree = TreeNode.ListToTree(list.Cast().ToList()); + PrintTree(tree); + } +} \ No newline at end of file diff --git a/ja/codes/csharp/utils/TreeNode.cs b/ja/codes/csharp/utils/TreeNode.cs new file mode 100644 index 000000000..9328140f6 --- /dev/null +++ b/ja/codes/csharp/utils/TreeNode.cs @@ -0,0 +1,67 @@ +/** + * File: TreeNode.cs + * Created Time: 2022-12-23 + * Author: haptear (haptear@hotmail.com) + */ + +namespace hello_algo.utils; + +/* 二分木ノードクラス */ +public class TreeNode(int? x) { + public int? val = x; // ノード値 + public int height; // ノードの高さ + public TreeNode? left; // 左子ノードへの参照 + public TreeNode? right; // 右子ノードへの参照 + + // シリアライズの符号化規則は以下を参照: + // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + // 二分木の配列表現: + // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + // 二分木の連結リスト表現: + // /——— 15 + // /——— 7 + // /——— 3 + // | \——— 6 + // | \——— 12 + // ——— 1 + // \——— 2 + // | /——— 9 + // \——— 4 + // \——— 8 + + /* リストを二分木にデシリアライズする: 再帰 */ + static TreeNode? ListToTreeDFS(List arr, int i) { + if (i < 0 || i >= arr.Count || !arr[i].HasValue) { + return null; + } + TreeNode root = new(arr[i]) { + left = ListToTreeDFS(arr, 2 * i + 1), + right = ListToTreeDFS(arr, 2 * i + 2) + }; + return root; + } + + /* リストを二分木にデシリアライズする */ + public static TreeNode? ListToTree(List arr) { + return ListToTreeDFS(arr, 0); + } + + /* 二分木をリストにシリアライズする: 再帰 */ + static void TreeToListDFS(TreeNode? root, int i, List res) { + if (root == null) + return; + while (i >= res.Count) { + res.Add(null); + } + res[i] = root.val; + TreeToListDFS(root.left, 2 * i + 1, res); + TreeToListDFS(root.right, 2 * i + 2, res); + } + + /* 二分木をリストにシリアライズする */ + public static List TreeToList(TreeNode root) { + List res = []; + TreeToListDFS(root, 0, res); + return res; + } +} diff --git a/ja/codes/csharp/utils/Vertex.cs b/ja/codes/csharp/utils/Vertex.cs new file mode 100644 index 000000000..7cff48f0d --- /dev/null +++ b/ja/codes/csharp/utils/Vertex.cs @@ -0,0 +1,30 @@ +/** + * File: Vertex.cs + * Created Time: 2023-02-06 + * Author: zjkung1123 (zjkung1123@gmail.com), krahets (krahets@163.com) + */ + +namespace hello_algo.utils; + +/* 頂点クラス */ +public class Vertex(int val) { + public int val = val; + + /* 値リスト vals を入力し、頂点リスト vets を返す */ + public static Vertex[] ValsToVets(int[] vals) { + Vertex[] vets = new Vertex[vals.Length]; + for (int i = 0; i < vals.Length; i++) { + vets[i] = new Vertex(vals[i]); + } + return vets; + } + + /* 頂点リスト vets を入力し、値リスト vals を返す */ + public static List VetsToVals(List vets) { + List vals = []; + foreach (Vertex vet in vets) { + vals.Add(vet.val); + } + return vals; + } +} diff --git a/ja/codes/dart/build.dart b/ja/codes/dart/build.dart new file mode 100644 index 000000000..7bd5b51a1 --- /dev/null +++ b/ja/codes/dart/build.dart @@ -0,0 +1,39 @@ +import 'dart:io'; + +void main() { + Directory foldPath = Directory('codes/dart/'); + List files = foldPath.listSync(); + int totalCount = 0; + int errorCount = 0; + for (var file in files) { + if (file.path.endsWith('build.dart')) continue; + if (file is File && file.path.endsWith('.dart')) { + totalCount++; + try { + Process.runSync('dart', [file.path]); + } catch (e) { + errorCount++; + print('Error: $e'); + print('File: ${file.path}'); + } + } else if (file is Directory) { + List subFiles = file.listSync(); + for (var subFile in subFiles) { + if (subFile is File && subFile.path.endsWith('.dart')) { + totalCount++; + try { + Process.runSync('dart', [subFile.path]); + } catch (e) { + errorCount++; + print('Error: $e'); + print('File: ${file.path}'); + } + } + } + } + } + + print('===== Build Complete ====='); + print('Total: $totalCount'); + print('Error: $errorCount'); +} diff --git a/ja/codes/dart/chapter_array_and_linkedlist/array.dart b/ja/codes/dart/chapter_array_and_linkedlist/array.dart new file mode 100644 index 000000000..03b263874 --- /dev/null +++ b/ja/codes/dart/chapter_array_and_linkedlist/array.dart @@ -0,0 +1,105 @@ +/** + * File: array.dart + * Created Time: 2023-01-20 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +// ignore_for_file: unused_local_variable + +import 'dart:math'; + +/* 要素へランダムアクセス */ +int randomAccess(List nums) { + // 区間 [0, nums.length) からランダムに 1 つの数を選ぶ + int randomIndex = Random().nextInt(nums.length); + // ランダムな要素を取得して返す + int randomNum = nums[randomIndex]; + return randomNum; +} + +/* 配列長を拡張する */ +List extend(List nums, int enlarge) { + // 拡張後の長さを持つ配列を初期化する + List res = List.filled(nums.length + enlarge, 0); + // 元の配列の全要素を新しい配列にコピー + for (var i = 0; i < nums.length; i++) { + res[i] = nums[i]; + } + // 拡張後の新しい配列を返す + return res; +} + +/* 配列の添字 index に要素 _num を挿入 */ +void insert(List nums, int _num, int index) { + // インデックス index 以降の全要素を 1 つ後ろへ移動する + for (var i = nums.length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // _num を index の位置の要素に代入 + nums[index] = _num; +} + +/* index の要素を削除する */ +void remove(List nums, int index) { + // インデックス index より後ろの全要素を 1 つ前へ移動する + for (var i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* 配列要素を走査する */ +void traverse(List nums) { + int count = 0; + // インデックスで配列を走査 + for (var i = 0; i < nums.length; i++) { + count += nums[i]; + } + // 配列要素を直接走査 + for (int _num in nums) { + count += _num; + } + // forEach メソッドで配列を走査する + nums.forEach((_num) { + count += _num; + }); +} + +/* 配列内で指定要素を探す */ +int find(List nums, int target) { + for (var i = 0; i < nums.length; i++) { + if (nums[i] == target) return i; + } + return -1; +} + +/* Driver Code */ +void main() { + /* 配列を初期化 */ + var arr = List.filled(5, 0); + print('配列 arr = $arr'); + List nums = [1, 3, 2, 5, 4]; + print('配列 nums = $nums'); + + /* ランダムアクセス */ + int randomNum = randomAccess(nums); + print('nums からランダムな要素 $randomNum を取得'); + + /* 長さを拡張 */ + nums = extend(nums, 3); + print('配列の長さを 8 に拡張し、nums = $nums を取得'); + + /* 要素を挿入する */ + insert(nums, 6, 3); + print("インデックス 3 に数字 6 を挿入し、nums = $nums を取得"); + + /* 要素を削除 */ + remove(nums, 2); + print("インデックス 2 の要素を削除し、nums = $nums を取得"); + + /* 配列を走査 */ + traverse(nums); + + /* 要素を探索する */ + int index = find(nums, 3); + print("nums で要素 3 を検索し、インデックス = $index を取得"); +} diff --git a/ja/codes/dart/chapter_array_and_linkedlist/linked_list.dart b/ja/codes/dart/chapter_array_and_linkedlist/linked_list.dart new file mode 100644 index 000000000..6a034095d --- /dev/null +++ b/ja/codes/dart/chapter_array_and_linkedlist/linked_list.dart @@ -0,0 +1,83 @@ +/** + * File: linked_list.dart + * Created Time: 2023-01-23 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import '../utils/list_node.dart'; +import '../utils/print_util.dart'; + +/* 連結リストでノード n0 の後ろにノード P を挿入する */ +void insert(ListNode n0, ListNode P) { + ListNode? n1 = n0.next; + P.next = n1; + n0.next = P; +} + +/* 連結リストでノード n0 の直後のノードを削除する */ +void remove(ListNode n0) { + if (n0.next == null) return; + // n0 -> P -> n1 + ListNode P = n0.next!; + ListNode? n1 = P.next; + n0.next = n1; +} + +/* 連結リスト内で index 番目のノードにアクセス */ +ListNode? access(ListNode? head, int index) { + for (var i = 0; i < index; i++) { + if (head == null) return null; + head = head.next; + } + return head; +} + +/* 連結リストで値が target の最初のノードを探す */ +int find(ListNode? head, int target) { + int index = 0; + while (head != null) { + if (head.val == target) { + return index; + } + head = head.next; + index++; + } + return -1; +} + +/* Driver Code */ +void main() { + // 連結リストを初期化する + // 各ノードを初期化する + ListNode n0 = ListNode(1); + ListNode n1 = ListNode(3); + ListNode n2 = ListNode(2); + ListNode n3 = ListNode(5); + ListNode n4 = ListNode(4); + // ノード間の参照を構築する + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; + + print('初期化した連結リストは'); + printLinkedList(n0); + + /* ノードを挿入 */ + insert(n0, ListNode(0)); + print('ノードを挿入した後の連結リストは'); + printLinkedList(n0); + + /* ノードを削除 */ + remove(n0); + print('ノードを削除した後の連結リストは'); + printLinkedList(n0); + + /* ノードにアクセス */ + ListNode? node = access(n0, 3); + print('連結リストのインデックス 3 にあるノードの値 = ${node!.val}'); + + /* ノードを探索 */ + int index = find(n0, 2); + print('連結リストで値 2 のノードのインデックス = $index'); +} diff --git a/ja/codes/dart/chapter_array_and_linkedlist/list.dart b/ja/codes/dart/chapter_array_and_linkedlist/list.dart new file mode 100644 index 000000000..f5a15a06f --- /dev/null +++ b/ja/codes/dart/chapter_array_and_linkedlist/list.dart @@ -0,0 +1,62 @@ +/** + * File: list.dart + * Created Time: 2023-01-24 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +// ignore_for_file: unused_local_variable + +/* Driver Code */ +void main() { + /* リストを初期化 */ + List nums = [1, 3, 2, 5, 4]; + print('リスト nums = $nums'); + + /* 要素にアクセス */ + int _num = nums[1]; + print('インデックス 1 の要素にアクセスし、_num = $_num を取得'); + + /* 要素を更新 */ + nums[1] = 0; + print('インデックス 1 の要素を 0 に更新し、nums = $nums を取得'); + + /* リストを空にする */ + nums.clear(); + print('リストを空にした後 nums = $nums'); + + /* 末尾に要素を追加 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + print('要素を追加した後 nums = $nums'); + + /* 中間に要素を挿入 */ + nums.insert(3, 6); + print('インデックス 3 に数字 6 を挿入し、nums = $nums を取得'); + + /* 要素を削除 */ + nums.removeAt(3); + print('インデックス 3 の要素を削除し、nums = $nums を取得'); + + /* インデックスでリストを走査 */ + int count = 0; + for (var i = 0; i < nums.length; i++) { + count += nums[i]; + } + /* リスト要素を直接走査 */ + count = 0; + for (var x in nums) { + count += x; + } + + /* 2 つのリストを連結する */ + List nums1 = [6, 8, 7, 10, 9]; + nums.addAll(nums1); + print('リスト nums1 を nums の後ろに連結し、nums = $nums を取得'); + + /* リストをソート */ + nums.sort(); + print('リストをソートした後 nums = $nums'); +} diff --git a/ja/codes/dart/chapter_array_and_linkedlist/my_list.dart b/ja/codes/dart/chapter_array_and_linkedlist/my_list.dart new file mode 100644 index 000000000..d2f6d7556 --- /dev/null +++ b/ja/codes/dart/chapter_array_and_linkedlist/my_list.dart @@ -0,0 +1,132 @@ +/** + * File: my_list.dart + * Created Time: 2023-02-05 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* リストクラス */ +class MyList { + late List _arr; // 配列(リスト要素を格納) + int _capacity = 10; // リスト容量 + int _size = 0; // リストの長さ(現在の要素数) + int _extendRatio = 2; // リスト拡張時の増加倍率 + + /* コンストラクタ */ + MyList() { + _arr = List.filled(_capacity, 0); + } + + /* リストの長さを取得(現在の要素数) */ + int size() => _size; + + /* リスト容量を取得する */ + int capacity() => _capacity; + + /* 要素にアクセス */ + int get(int index) { + if (index >= _size) throw RangeError('インデックスが範囲外です'); + return _arr[index]; + } + + /* 要素を更新 */ + void set(int index, int _num) { + if (index >= _size) throw RangeError('インデックスが範囲外です'); + _arr[index] = _num; + } + + /* 末尾に要素を追加 */ + void add(int _num) { + // 要素数が容量を超えると、拡張機構が発動する + if (_size == _capacity) extendCapacity(); + _arr[_size] = _num; + // 要素数を更新 + _size++; + } + + /* 中間に要素を挿入 */ + void insert(int index, int _num) { + if (index >= _size) throw RangeError('インデックスが範囲外です'); + // 要素数が容量を超えると、拡張機構が発動する + if (_size == _capacity) extendCapacity(); + // index 以降の要素をすべて 1 つ後ろへずらす + for (var j = _size - 1; j >= index; j--) { + _arr[j + 1] = _arr[j]; + } + _arr[index] = _num; + // 要素数を更新 + _size++; + } + + /* 要素を削除 */ + int remove(int index) { + if (index >= _size) throw RangeError('インデックスが範囲外です'); + int _num = _arr[index]; + // インデックス index より後の要素をすべて 1 つ前に移動する + for (var j = index; j < _size - 1; j++) { + _arr[j] = _arr[j + 1]; + } + // 要素数を更新 + _size--; + // 削除された要素を返す + return _num; + } + + /* リストの拡張 */ + void extendCapacity() { + // 元の配列の `_extendRatio` 倍の長さを持つ新しい配列を作成する + final _newNums = List.filled(_capacity * _extendRatio, 0); + // 元の配列を新しい配列にコピー + List.copyRange(_newNums, 0, _arr); + // `_arr` の参照を更新 + _arr = _newNums; + // リストの容量を更新 + _capacity = _arr.length; + } + + /* リストを配列に変換する */ + List toArray() { + List arr = []; + for (var i = 0; i < _size; i++) { + arr.add(get(i)); + } + return arr; + } +} + +/* Driver Code */ +void main() { + /* リストを初期化 */ + MyList nums = MyList(); + /* 末尾に要素を追加 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + print( + 'リスト nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長さ = ${nums.size()}'); + + /* 中間に要素を挿入 */ + nums.insert(3, 6); + print('インデックス 3 に数字 6 を挿入し、nums = ${nums.toArray()} を取得'); + + /* 要素を削除 */ + nums.remove(3); + print('インデックス 3 の要素を削除し、nums = ${nums.toArray()} を取得'); + + /* 要素にアクセス */ + int _num = nums.get(1); + print('インデックス 1 の要素にアクセスし、_num = $_num を取得'); + + /* 要素を更新 */ + nums.set(1, 0); + print('インデックス 1 の要素を 0 に更新し、nums = ${nums.toArray()} を取得'); + + /* 拡張機構をテストする */ + for (var i = 0; i < 10; i++) { + // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する + nums.add(i); + } + print( + '容量拡張後のリスト nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長さ = ${nums.size()}'); +} diff --git a/ja/codes/dart/chapter_backtracking/n_queens.dart b/ja/codes/dart/chapter_backtracking/n_queens.dart new file mode 100644 index 000000000..7c4271cc7 --- /dev/null +++ b/ja/codes/dart/chapter_backtracking/n_queens.dart @@ -0,0 +1,75 @@ +/** + * File: n_queens.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* バックトラッキング:N クイーン */ +void backtrack( + int row, + int n, + List> state, + List>> res, + List cols, + List diags1, + List diags2, +) { + // すべての行への配置が完了したら、解を記録する + if (row == n) { + List> copyState = []; + for (List sRow in state) { + copyState.add(List.from(sRow)); + } + res.add(copyState); + return; + } + // すべての列を走査 + for (int col = 0; col < n; col++) { + // このマスに対応する主対角線と副対角線を計算 + int diag1 = row - col + n - 1; + int diag2 = row + col; + // 枝刈り:そのマスの列、主対角線、副対角線にクイーンがあってはならない + if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { + // 試行:そのマスにクイーンを置く + state[row][col] = "Q"; + cols[col] = true; + diags1[diag1] = true; + diags2[diag2] = true; + // 次の行に配置する + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 戻す:そのマスを空きマスに戻す + state[row][col] = "#"; + cols[col] = false; + diags1[diag1] = false; + diags2[diag2] = false; + } + } +} + +/* N クイーンを解く */ +List>> nQueens(int n) { + // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す + List> state = List.generate(n, (index) => List.filled(n, "#")); + List cols = List.filled(n, false); // 列にクイーンがあるか記録 + List diags1 = List.filled(2 * n - 1, false); // 主対角線にクイーンがあるかを記録 + List diags2 = List.filled(2 * n - 1, false); // 副対角線にクイーンがあるかを記録 + List>> res = []; + + backtrack(0, n, state, res, cols, diags1, diags2); + + return res; +} + +/* Driver Code */ +void main() { + int n = 4; + List>> res = nQueens(n); + print("チェス盤の縦横サイズの入力値は $n"); + print("クイーンの配置パターンは全部で ${res.length} 通り"); + for (List> state in res) { + print("--------------------"); + for (List row in state) { + print(row); + } + } +} diff --git a/ja/codes/dart/chapter_backtracking/permutations_i.dart b/ja/codes/dart/chapter_backtracking/permutations_i.dart new file mode 100644 index 000000000..1a7eb5759 --- /dev/null +++ b/ja/codes/dart/chapter_backtracking/permutations_i.dart @@ -0,0 +1,51 @@ +/** + * File: permutations_i.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* バックトラッキング:順列 I */ +void backtrack( + List state, + List choices, + List selected, + List> res, +) { + // 状態の長さが要素数に等しければ、解を記録 + if (state.length == choices.length) { + res.add(List.from(state)); + return; + } + // すべての選択肢を走査 + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // 枝刈り:要素の重複選択を許可しない + if (!selected[i]) { + // 試行: 選択を行い、状態を更新 + selected[i] = true; + state.add(choice); + // 次の選択へ進む + backtrack(state, choices, selected, res); + // バックトラック:選択を取り消し、前の状態に戻す + selected[i] = false; + state.removeLast(); + } + } +} + +/* 全順列 I */ +List> permutationsI(List nums) { + List> res = []; + backtrack([], nums, List.filled(nums.length, false), res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [1, 2, 3]; + + List> res = permutationsI(nums); + + print("入力配列 nums = $nums"); + print("すべての順列 res = $res"); +} diff --git a/ja/codes/dart/chapter_backtracking/permutations_ii.dart b/ja/codes/dart/chapter_backtracking/permutations_ii.dart new file mode 100644 index 000000000..17377367e --- /dev/null +++ b/ja/codes/dart/chapter_backtracking/permutations_ii.dart @@ -0,0 +1,53 @@ +/** + * File: permutations_ii.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* バックトラッキング:順列 II */ +void backtrack( + List state, + List choices, + List selected, + List> res, +) { + // 状態の長さが要素数に等しければ、解を記録 + if (state.length == choices.length) { + res.add(List.from(state)); + return; + } + // すべての選択肢を走査 + Set duplicated = {}; + for (int i = 0; i < choices.length; i++) { + int choice = choices[i]; + // 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない + if (!selected[i] && !duplicated.contains(choice)) { + // 試行: 選択を行い、状態を更新 + duplicated.add(choice); // 選択済みの要素値を記録 + selected[i] = true; + state.add(choice); + // 次の選択へ進む + backtrack(state, choices, selected, res); + // バックトラック:選択を取り消し、前の状態に戻す + selected[i] = false; + state.removeLast(); + } + } +} + +/* 全順列 II */ +List> permutationsII(List nums) { + List> res = []; + backtrack([], nums, List.filled(nums.length, false), res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [1, 2, 2]; + + List> res = permutationsII(nums); + + print("入力配列 nums = $nums"); + print("すべての順列 res = $res"); +} diff --git a/ja/codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart b/ja/codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart new file mode 100644 index 000000000..2647394d0 --- /dev/null +++ b/ja/codes/dart/chapter_backtracking/preorder_traversal_i_compact.dart @@ -0,0 +1,35 @@ +/** + * File: preorder_traversal_i_compact.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 前順走査:例題 1 */ +void preOrder(TreeNode? root, List res) { + if (root == null) { + return; + } + if (root.val == 7) { + // 解を記録 + res.add(root); + } + preOrder(root.left, res); + preOrder(root.right, res); +} + +/* Driver Code */ +void main() { + TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); + print("\n二分木を初期化"); + printTree(root); + + // 先行順走査 + List res = []; + preOrder(root, res); + + print("\n値が 7 のノードをすべて出力"); + print(List.generate(res.length, (i) => res[i].val)); +} diff --git a/ja/codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart b/ja/codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart new file mode 100644 index 000000000..e29576a8e --- /dev/null +++ b/ja/codes/dart/chapter_backtracking/preorder_traversal_ii_compact.dart @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_ii_compact.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 前順走査:例題 2 */ +void preOrder( + TreeNode? root, + List path, + List> res, +) { + if (root == null) { + return; + } + + // 試す + path.add(root); + if (root.val == 7) { + // 解を記録 + res.add(List.from(path)); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // バックトラック + path.removeLast(); +} + +/* Driver Code */ +void main() { + TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); + print("\n二分木を初期化"); + printTree(root); + + // 先行順走査 + List path = []; + List> res = []; + preOrder(root, path, res); + + print("\nルートノードからノード 7 までのすべての経路を出力"); + for (List vals in res) { + print(List.generate(vals.length, (i) => vals[i].val)); + } +} diff --git a/ja/codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart b/ja/codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart new file mode 100644 index 000000000..dc9fc5749 --- /dev/null +++ b/ja/codes/dart/chapter_backtracking/preorder_traversal_iii_compact.dart @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_iii_compact.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 前順走査:例題 3 */ +void preOrder( + TreeNode? root, + List path, + List> res, +) { + if (root == null || root.val == 3) { + return; + } + + // 試す + path.add(root); + if (root.val == 7) { + // 解を記録 + res.add(List.from(path)); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // バックトラック + path.removeLast(); +} + +/* Driver Code */ +void main() { + TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); + print("\n二分木を初期化"); + printTree(root); + + // 先行順走査 + List path = []; + List> res = []; + preOrder(root, path, res); + + print("\nルートノードからノード 7 までのすべての経路を出力"); + for (List vals in res) { + print(List.generate(vals.length, (i) => vals[i].val)); + } +} diff --git a/ja/codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart b/ja/codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart new file mode 100644 index 000000000..efc55b935 --- /dev/null +++ b/ja/codes/dart/chapter_backtracking/preorder_traversal_iii_template.dart @@ -0,0 +1,73 @@ +/** + * File: preorder_traversal_iii_template.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 現在の状態が解かどうかを判定 */ +bool isSolution(List state) { + return state.isNotEmpty && state.last.val == 7; +} + +/* 解を記録 */ +void recordSolution(List state, List> res) { + res.add(List.from(state)); +} + +/* 現在の状態で、この選択が有効かどうかを判定 */ +bool isValid(List state, TreeNode? choice) { + return choice != null && choice.val != 3; +} + +/* 状態を更新 */ +void makeChoice(List state, TreeNode? choice) { + state.add(choice!); +} + +/* 状態を元に戻す */ +void undoChoice(List state, TreeNode? choice) { + state.removeLast(); +} + +/* バックトラッキング:例題 3 */ +void backtrack( + List state, + List choices, + List> res, +) { + // 解かどうかを確認 + if (isSolution(state)) { + // 解を記録 + recordSolution(state, res); + } + // すべての選択肢を走査 + for (TreeNode? choice in choices) { + // 枝刈り:選択が妥当かを確認する + if (isValid(state, choice)) { + // 試行: 選択を行い、状態を更新 + makeChoice(state, choice); + // 次の選択へ進む + backtrack(state, [choice!.left, choice.right], res); + // バックトラック:選択を取り消し、前の状態に戻す + undoChoice(state, choice); + } + } +} + +/* Driver Code */ +void main() { + TreeNode? root = listToTree([1, 7, 3, 4, 5, 6, 7]); + print("\n二分木を初期化"); + printTree(root); + + // バックトラッキング法 + List> res = []; + backtrack([], [root!], res); + print("\nルートノードからノード 7 までのすべての経路を出力し、経路には値が 3 のノードを含まない"); + for (List path in res) { + print(List.from(path.map((e) => e.val))); + } +} diff --git a/ja/codes/dart/chapter_backtracking/subset_sum_i.dart b/ja/codes/dart/chapter_backtracking/subset_sum_i.dart new file mode 100644 index 000000000..422ebdff1 --- /dev/null +++ b/ja/codes/dart/chapter_backtracking/subset_sum_i.dart @@ -0,0 +1,56 @@ +/** + * File: subset_sum_i.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* バックトラッキング:部分和 I */ +void backtrack( + List state, + int target, + List choices, + int start, + List> res, +) { + // 部分集合の和が target に等しければ、解を記録 + if (target == 0) { + res.add(List.from(state)); + return; + } + // すべての選択肢を走査 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + for (int i = start; i < choices.length; i++) { + // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため + if (target - choices[i] < 0) { + break; + } + // 試す:選択を行い、target と start を更新 + state.add(choices[i]); + // 次の選択へ進む + backtrack(state, target - choices[i], choices, i, res); + // バックトラック:選択を取り消し、前の状態に戻す + state.removeLast(); + } +} + +/* 部分和 I を解く */ +List> subsetSumI(List nums, int target) { + List state = []; // 状態(部分集合) + nums.sort(); // nums をソート + int start = 0; // 開始点を走査 + List> res = []; // 結果リスト(部分集合のリスト) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [3, 4, 5]; + int target = 9; + + List> res = subsetSumI(nums, target); + + print("入力配列 nums = $nums, target = $target"); + print("和が $target に等しいすべての部分集合 res = $res"); +} diff --git a/ja/codes/dart/chapter_backtracking/subset_sum_i_naive.dart b/ja/codes/dart/chapter_backtracking/subset_sum_i_naive.dart new file mode 100644 index 000000000..432411446 --- /dev/null +++ b/ja/codes/dart/chapter_backtracking/subset_sum_i_naive.dart @@ -0,0 +1,54 @@ +/** + * File: subset_sum_i_naive.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* バックトラッキング:部分和 I */ +void backtrack( + List state, + int target, + int total, + List choices, + List> res, +) { + // 部分集合の和が target に等しければ、解を記録 + if (total == target) { + res.add(List.from(state)); + return; + } + // すべての選択肢を走査 + for (int i = 0; i < choices.length; i++) { + // 枝刈り:部分和が target を超える場合はその選択をスキップする + if (total + choices[i] > target) { + continue; + } + // 試行:選択を行い、要素と total を更新する + state.add(choices[i]); + // 次の選択へ進む + backtrack(state, target, total + choices[i], choices, res); + // バックトラック:選択を取り消し、前の状態に戻す + state.removeLast(); + } +} + +/* 部分和 I を解く(重複部分集合を含む) */ +List> subsetSumINaive(List nums, int target) { + List state = []; // 状態(部分集合) + int total = 0; // 要素の合計 + List> res = []; // 結果リスト(部分集合のリスト) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [3, 4, 5]; + int target = 9; + + List> res = subsetSumINaive(nums, target); + + print("入力配列 nums = $nums, target = $target"); + print("和が $target に等しいすべての部分集合 res = $res"); + print("この方法が出力する結果には重複する集合が含まれることに注意してください"); +} diff --git a/ja/codes/dart/chapter_backtracking/subset_sum_ii.dart b/ja/codes/dart/chapter_backtracking/subset_sum_ii.dart new file mode 100644 index 000000000..a3bfa0aca --- /dev/null +++ b/ja/codes/dart/chapter_backtracking/subset_sum_ii.dart @@ -0,0 +1,61 @@ +/** + * File: subset_sum_ii.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* バックトラッキング:部分和 II */ +void backtrack( + List state, + int target, + List choices, + int start, + List> res, +) { + // 部分集合の和が target に等しければ、解を記録 + if (target == 0) { + res.add(List.from(state)); + return; + } + // すべての選択肢を走査 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける + for (int i = start; i < choices.length; i++) { + // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため + if (target - choices[i] < 0) { + break; + } + // 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // 試す:選択を行い、target と start を更新 + state.add(choices[i]); + // 次の選択へ進む + backtrack(state, target - choices[i], choices, i + 1, res); + // バックトラック:選択を取り消し、前の状態に戻す + state.removeLast(); + } +} + +/* 部分和 II を解く */ +List> subsetSumII(List nums, int target) { + List state = []; // 状態(部分集合) + nums.sort(); // nums をソート + int start = 0; // 開始点を走査 + List> res = []; // 結果リスト(部分集合のリスト) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +void main() { + List nums = [4, 4, 5]; + int target = 9; + + List> res = subsetSumII(nums, target); + + print("入力配列 nums = $nums, target = $target"); + print("和が $target に等しいすべての部分集合 res = $res"); +} diff --git a/ja/codes/dart/chapter_computational_complexity/iteration.dart b/ja/codes/dart/chapter_computational_complexity/iteration.dart new file mode 100644 index 000000000..c23fafdc1 --- /dev/null +++ b/ja/codes/dart/chapter_computational_complexity/iteration.dart @@ -0,0 +1,72 @@ +/** + * File: iteration.dart + * Created Time: 2023-08-27 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* for ループ */ +int forLoop(int n) { + int res = 0; + // 1, 2, ..., n-1, n を順に加算する + for (int i = 1; i <= n; i++) { + res += i; + } + return res; +} + +/* while ループ */ +int whileLoop(int n) { + int res = 0; + int i = 1; // 条件変数を初期化する + // 1, 2, ..., n-1, n を順に加算する + while (i <= n) { + res += i; + i++; // 条件変数を更新する + } + return res; +} + +/* while ループ(2回更新) */ +int whileLoopII(int n) { + int res = 0; + int i = 1; // 条件変数を初期化する + // 1, 4, 10, ... を順に加算する + while (i <= n) { + res += i; + // 条件変数を更新する + i++; + i *= 2; + } + return res; +} + +/* 二重 for ループ */ +String nestedForLoop(int n) { + String res = ""; + // i = 1, 2, ..., n-1, n とループする + for (int i = 1; i <= n; i++) { + // j = 1, 2, ..., n-1, n とループする + for (int j = 1; j <= n; j++) { + res += "($i, $j), "; + } + } + return res; +} + +/* Driver Code */ +void main() { + int n = 5; + int res; + + res = forLoop(n); + print("\nfor ループの合計結果 res = $res"); + + res = whileLoop(n); + print("\nwhile ループの合計結果 res = $res"); + + res = whileLoopII(n); + print("\nwhile ループ(2 回更新)の合計結果 res = $res"); + + String resStr = nestedForLoop(n); + print("\n二重 for ループの結果 $resStr"); +} diff --git a/ja/codes/dart/chapter_computational_complexity/recursion.dart b/ja/codes/dart/chapter_computational_complexity/recursion.dart new file mode 100644 index 000000000..4e13ca13e --- /dev/null +++ b/ja/codes/dart/chapter_computational_complexity/recursion.dart @@ -0,0 +1,70 @@ +/** + * File: recursion.dart + * Created Time: 2023-08-27 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 再帰 */ +int recur(int n) { + // 終了条件 + if (n == 1) return 1; + // 再帰:再帰呼び出し + int res = recur(n - 1); + // 帰りがけ:結果を返す + return n + res; +} + +/* 反復で再帰を模擬する */ +int forLoopRecur(int n) { + // 明示的なスタックを使ってシステムコールスタックを模擬する + List stack = []; + int res = 0; + // 再帰:再帰呼び出し + for (int i = n; i > 0; i--) { + // 「スタックへのプッシュ」で「再帰」を模擬する + stack.add(i); + } + // 帰りがけ:結果を返す + while (!stack.isEmpty) { + // 「スタックから取り出す操作」で「帰り」をシミュレート + res += stack.removeLast(); + } + // res = 1+2+3+...+n + return res; +} + +/* 末尾再帰 */ +int tailRecur(int n, int res) { + // 終了条件 + if (n == 0) return res; + // 末尾再帰呼び出し + return tailRecur(n - 1, res + n); +} + +/* フィボナッチ数列:再帰 */ +int fib(int n) { + // 終了条件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) return n - 1; + // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す + int res = fib(n - 1) + fib(n - 2); + // 結果 f(n) を返す + return res; +} + +/* Driver Code */ +void main() { + int n = 5; + int res; + + res = recur(n); + print("\n再帰関数の合計結果 res = $res"); + + res = tailRecur(n, 0); + print("\n末尾再帰関数の合計結果 res = $res"); + + res = forLoopRecur(n); + print("\n反復で再帰をシミュレートした合計結果 res = $res"); + + res = fib(n); + print("\nフィボナッチ数列の第 $n 項は $res"); +} diff --git a/ja/codes/dart/chapter_computational_complexity/space_complexity.dart b/ja/codes/dart/chapter_computational_complexity/space_complexity.dart new file mode 100644 index 000000000..e703a87e1 --- /dev/null +++ b/ja/codes/dart/chapter_computational_complexity/space_complexity.dart @@ -0,0 +1,106 @@ +/** + * File: space_complexity.dart + * Created Time: 2023-2-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +// ignore_for_file: unused_local_variable + +import 'dart:collection'; +import '../utils/list_node.dart'; +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 関数 */ +int function() { + // 何らかの処理を行う + return 0; +} + +/* 定数階 */ +void constant(int n) { + // 定数、変数、オブジェクトは O(1) の空間を占める + final int a = 0; + int b = 0; + List nums = List.filled(10000, 0); + ListNode node = ListNode(0); + // ループ内の変数は O(1) の空間を占める + for (var i = 0; i < n; i++) { + int c = 0; + } + // ループ内の関数は O(1) の空間を占める + for (var i = 0; i < n; i++) { + function(); + } +} + +/* 線形階 */ +void linear(int n) { + // 長さ n の配列は O(n) の空間を使用 + List nums = List.filled(n, 0); + // 長さ n のリストは O(n) の空間を使用 + List nodes = []; + for (var i = 0; i < n; i++) { + nodes.add(ListNode(i)); + } + // 長さ n のハッシュテーブルは O(n) の空間を使用 + Map map = HashMap(); + for (var i = 0; i < n; i++) { + map.putIfAbsent(i, () => i.toString()); + } +} + +/* 線形時間(再帰実装) */ +void linearRecur(int n) { + print('再帰 n = $n'); + if (n == 1) return; + linearRecur(n - 1); +} + +/* 二乗階 */ +void quadratic(int n) { + // 行列は O(n^2) の空間を使用する + List> numMatrix = List.generate(n, (_) => List.filled(n, 0)); + // 二次元リストは O(n^2) の空間を使用 + List> numList = []; + for (var i = 0; i < n; i++) { + List tmp = []; + for (int j = 0; j < n; j++) { + tmp.add(0); + } + numList.add(tmp); + } +} + +/* 二次時間(再帰実装) */ +int quadraticRecur(int n) { + if (n <= 0) return 0; + List nums = List.filled(n, 0); + print('再帰 n = $n における nums の長さ = ${nums.length}'); + return quadraticRecur(n - 1); +} + +/* 指数時間(完全二分木の構築) */ +TreeNode? buildTree(int n) { + if (n == 0) return null; + TreeNode root = TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +void main() { + int n = 5; + // 定数階 + constant(n); + // 線形階 + linear(n); + linearRecur(n); + // 二乗階 + quadratic(n); + quadraticRecur(n); + // 指数オーダー + TreeNode? root = buildTree(n); + printTree(root); +} diff --git a/ja/codes/dart/chapter_computational_complexity/time_complexity.dart b/ja/codes/dart/chapter_computational_complexity/time_complexity.dart new file mode 100644 index 000000000..27fa53b7e --- /dev/null +++ b/ja/codes/dart/chapter_computational_complexity/time_complexity.dart @@ -0,0 +1,165 @@ +/** + * File: time_complexity.dart + * Created Time: 2023-02-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +// ignore_for_file: unused_local_variable + +/* 定数階 */ +int constant(int n) { + int count = 0; + int size = 100000; + for (var i = 0; i < size; i++) { + count++; + } + return count; +} + +/* 線形階 */ +int linear(int n) { + int count = 0; + for (var i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 線形時間(配列を走査) */ +int arrayTraversal(List nums) { + int count = 0; + // ループ回数は配列長に比例する + for (var _num in nums) { + count++; + } + return count; +} + +/* 二乗階 */ +int quadratic(int n) { + int count = 0; + // ループ回数はデータサイズ n の二乗に比例する + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* 二次時間(バブルソート) */ +int bubbleSort(List nums) { + int count = 0; // カウンタ + // 外側のループ:未ソート区間は [0, i] + for (var i = nums.length - 1; i > 0; i--) { + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for (var j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // nums[j] と nums[j + 1] を交換 + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 要素交換には 3 回の単位操作が含まれる + } + } + } + return count; +} + +/* 指数時間(ループ実装) */ +int exponential(int n) { + int count = 0, base = 1; + // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する + for (var i = 0; i < n; i++) { + for (var j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* 指数時間(再帰実装) */ +int expRecur(int n) { + if (n == 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* 対数時間(ループ実装) */ +int logarithmic(int n) { + int count = 0; + while (n > 1) { + n = n ~/ 2; + count++; + } + return count; +} + +/* 対数時間(再帰実装) */ +int logRecur(int n) { + if (n <= 1) return 0; + return logRecur(n ~/ 2) + 1; +} + +/* 線形対数時間 */ +int linearLogRecur(int n) { + if (n <= 1) return 1; + int count = linearLogRecur(n ~/ 2) + linearLogRecur(n ~/ 2); + for (var i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 階乗時間(再帰実装) */ +int factorialRecur(int n) { + if (n == 0) return 1; + int count = 0; + // 1個から n 個に分裂 + for (var i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +void main() { + // n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる + int n = 8; + print('入力データサイズ n = $n'); + + int count = constant(n); + print('定数時間の操作回数 = $count'); + + count = linear(n); + print('線形時間の操作回数 = $count'); + + count = arrayTraversal(List.filled(n, 0)); + print('線形時間(配列走査)の操作回数 = $count'); + + count = quadratic(n); + print('二次時間の操作回数 = $count'); + final nums = List.filled(n, 0); + for (int i = 0; i < n; i++) { + nums[i] = n - i; // [n,n-1,...,2,1] + } + count = bubbleSort(nums); + print('二次時間(バブルソート)の操作回数 = $count'); + + count = exponential(n); + print('指数時間(ループ実装)の操作回数 = $count'); + count = expRecur(n); + print('指数時間(再帰実装)の操作回数 = $count'); + + count = logarithmic(n); + print('対数時間(ループ実装)の操作回数 = $count'); + count = logRecur(n); + print('対数時間(再帰実装)の操作回数 = $count'); + + count = linearLogRecur(n); + print('線形対数時間(再帰実装)の操作回数 = $count'); + + count = factorialRecur(n); + print('階乗時間(再帰実装)の操作回数 = $count'); +} diff --git a/ja/codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart b/ja/codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart new file mode 100644 index 000000000..fbbf0cada --- /dev/null +++ b/ja/codes/dart/chapter_computational_complexity/worst_best_time_complexity.dart @@ -0,0 +1,40 @@ +/** + * File: worst_best_time_complexity.dart + * Created Time: 2023-02-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 */ +List randomNumbers(int n) { + final nums = List.filled(n, 0); + // 配列 nums = { 1, 2, 3, ..., n } を生成 + for (var i = 0; i < n; i++) { + nums[i] = i + 1; + } + // 配列要素をランダムにシャッフル + nums.shuffle(); + + return nums; +} + +/* 配列 nums 内で数値 1 のインデックスを探す */ +int findOne(List nums) { + for (var i = 0; i < nums.length; i++) { + // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる + // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる + if (nums[i] == 1) return i; + } + + return -1; +} + +/* Driver Code */ +void main() { + for (var i = 0; i < 10; i++) { + int n = 100; + final nums = randomNumbers(n); + int index = findOne(nums); + print('\n配列 [ 1, 2, ..., n ] をシャッフルした後 = $nums'); + print('数字 1 のインデックスは + $index'); + } +} diff --git a/ja/codes/dart/chapter_divide_and_conquer/binary_search_recur.dart b/ja/codes/dart/chapter_divide_and_conquer/binary_search_recur.dart new file mode 100644 index 000000000..d3d1694ff --- /dev/null +++ b/ja/codes/dart/chapter_divide_and_conquer/binary_search_recur.dart @@ -0,0 +1,42 @@ +/** + * File: binary_search_recur.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 二分探索:問題 f(i, j) */ +int dfs(List nums, int target, int i, int j) { + // 区間が空なら対象要素は存在しないので -1 を返す + if (i > j) { + return -1; + } + // 中点インデックス m を計算 + int m = (i + j) ~/ 2; + if (nums[m] < target) { + // 部分問題 f(m+1, j) を再帰的に解く + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // 部分問題 f(i, m-1) を再帰的に解く + return dfs(nums, target, i, m - 1); + } else { + // 目標要素が見つかったらそのインデックスを返す + return m; + } +} + +/* 二分探索 */ +int binarySearch(List nums, int target) { + int n = nums.length; + // 問題 f(0, n-1) を解く + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +void main() { + int target = 6; + List nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + // 二分探索(両閉区間) + int index = binarySearch(nums, target); + print("目標要素 6 のインデックス = $index"); +} diff --git a/ja/codes/dart/chapter_divide_and_conquer/build_tree.dart b/ja/codes/dart/chapter_divide_and_conquer/build_tree.dart new file mode 100644 index 000000000..d39cefa2f --- /dev/null +++ b/ja/codes/dart/chapter_divide_and_conquer/build_tree.dart @@ -0,0 +1,55 @@ +/** + * File: build_tree.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 二分木を構築:分割統治 */ +TreeNode? dfs( + List preorder, + Map inorderMap, + int i, + int l, + int r, +) { + // 部分木区間が空なら終了する + if (r - l < 0) { + return null; + } + // ルートノードを初期化する + TreeNode? root = TreeNode(preorder[i]); + // m を求めて左右部分木を分割する + int m = inorderMap[preorder[i]]!; + // 部分問題:左部分木を構築する + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // 部分問題:右部分木を構築する + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 根ノードを返す + return root; +} + +/* 二分木を構築 */ +TreeNode? buildTree(List preorder, List inorder) { + // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する + Map inorderMap = {}; + for (int i = 0; i < inorder.length; i++) { + inorderMap[inorder[i]] = i; + } + TreeNode? root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; +} + +/* Driver Code */ +void main() { + List preorder = [3, 9, 2, 1, 7]; + List inorder = [9, 3, 1, 2, 7]; + print("前順走査 = $preorder"); + print("中順走査 = $inorder"); + + TreeNode? root = buildTree(preorder, inorder); + print("構築した二分木は:"); + printTree(root!); +} diff --git a/ja/codes/dart/chapter_divide_and_conquer/hanota.dart b/ja/codes/dart/chapter_divide_and_conquer/hanota.dart new file mode 100644 index 000000000..999ccf229 --- /dev/null +++ b/ja/codes/dart/chapter_divide_and_conquer/hanota.dart @@ -0,0 +1,54 @@ +/** + * File: hanota.dart + * Created Time: 2023-08-10 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 円盤を 1 枚移動 */ +void move(List src, List tar) { + // src の上から円盤を1枚取り出す + int pan = src.removeLast(); + // 円盤を tar の上に置く + tar.add(pan); +} + +/* ハノイの塔の問題 f(i) を解く */ +void dfs(int i, List src, List buf, List tar) { + // src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す + if (i == 1) { + move(src, tar); + return; + } + // 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す + dfs(i - 1, src, tar, buf); + // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す + move(src, tar); + // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す + dfs(i - 1, buf, src, tar); +} + +/* ハノイの塔を解く */ +void solveHanota(List A, List B, List C) { + int n = A.length; + // A の上から n 枚の円盤を B を介して C へ移す + dfs(n, A, B, C); +} + +/* Driver Code */ +void main() { + // リスト末尾が柱の頂上 + List A = [5, 4, 3, 2, 1]; + List B = []; + List C = []; + print("初期状態:"); + print("A = $A"); + print("B = $B"); + print("C = $C"); + + solveHanota(A, B, C); + + print("円盤の移動完了後:"); + print("A = $A"); + print("B = $B"); + print("C = $C"); +} diff --git a/ja/codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart b/ja/codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart new file mode 100644 index 000000000..d1e70f3af --- /dev/null +++ b/ja/codes/dart/chapter_dynamic_programming/climbing_stairs_backtrack.dart @@ -0,0 +1,39 @@ +/** + * File: climbing_stairs_backtrack.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* バックトラッキング */ +void backtrack(List choices, int state, int n, List res) { + // 第 n 段に到達したら、方法数を 1 増やす + if (state == n) { + res[0]++; + } + // すべての選択肢を走査 + for (int choice in choices) { + // 枝刈り: 第 n 段を超えないようにする + if (state + choice > n) continue; + // 試行: 選択を行い、状態を更新 + backtrack(choices, state + choice, n, res); + // バックトラック + } +} + +/* 階段登り:バックトラッキング */ +int climbingStairsBacktrack(int n) { + List choices = [1, 2]; // 1 段または 2 段上ることを選べる + int state = 0; // 第 0 段から上り始める + List res = []; + res.add(0); // res[0] を使って方法数を記録する + backtrack(choices, state, n, res); + return res[0]; +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsBacktrack(n); + print("$n 段の階段の登り方は全部で $res 通り"); +} diff --git a/ja/codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart b/ja/codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart new file mode 100644 index 000000000..fab63b7bf --- /dev/null +++ b/ja/codes/dart/chapter_dynamic_programming/climbing_stairs_constraint_dp.dart @@ -0,0 +1,33 @@ +/** + * File: climbing_stairs_constraint_dp.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 制約付き階段登り:動的計画法 */ +int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 部分問題の解を保存するために dp テーブルを初期化 + List> dp = List.generate(n + 1, (index) => List.filled(3, 0)); + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for (int i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsConstraintDP(n); + print("$n 段の階段の登り方は全部で $res 通り"); +} diff --git a/ja/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart b/ja/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart new file mode 100644 index 000000000..07de99325 --- /dev/null +++ b/ja/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs.dart @@ -0,0 +1,27 @@ +/** + * File: climbing_stairs_dfs.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 検索 */ +int dfs(int i) { + // dp[1] と dp[2] は既知なので返す + if (i == 1 || i == 2) return i; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* 階段登り:探索 */ +int climbingStairsDFS(int n) { + return dfs(n); +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsDFS(n); + print("$n 段の階段の登り方は全部で $res 通り"); +} diff --git a/ja/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart b/ja/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart new file mode 100644 index 000000000..23e2371a8 --- /dev/null +++ b/ja/codes/dart/chapter_dynamic_programming/climbing_stairs_dfs_mem.dart @@ -0,0 +1,33 @@ +/** + * File: climbing_stairs_dfs_mem.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* メモ化探索 */ +int dfs(int i, List mem) { + // dp[1] と dp[2] は既知なので返す + if (i == 1 || i == 2) return i; + // dp[i] の記録があれば、それをそのまま返す + if (mem[i] != -1) return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + int count = dfs(i - 1, mem) + dfs(i - 2, mem); + // dp[i] を記録する + mem[i] = count; + return count; +} + +/* 階段登り:メモ化探索 */ +int climbingStairsDFSMem(int n) { + // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す + List mem = List.filled(n + 1, -1); + return dfs(n, mem); +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsDFSMem(n); + print("$n 段の階段の登り方は全部で $res 通り"); +} diff --git a/ja/codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart b/ja/codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart new file mode 100644 index 000000000..8423a0211 --- /dev/null +++ b/ja/codes/dart/chapter_dynamic_programming/climbing_stairs_dp.dart @@ -0,0 +1,43 @@ +/** + * File: climbing_stairs_dp.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 階段登り:動的計画法 */ +int climbingStairsDP(int n) { + if (n == 1 || n == 2) return n; + // 部分問題の解を保存するために dp テーブルを初期化 + List dp = List.filled(n + 1, 0); + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1] = 1; + dp[2] = 2; + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for (int i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +/* 階段登り:空間最適化した動的計画法 */ +int climbingStairsDPComp(int n) { + if (n == 1 || n == 2) return n; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +void main() { + int n = 9; + + int res = climbingStairsDP(n); + print("$n 段の階段の登り方は全部で $res 通り"); + + res = climbingStairsDPComp(n); + print("$n 段の階段の登り方は全部で $res 通り"); +} diff --git a/ja/codes/dart/chapter_dynamic_programming/coin_change.dart b/ja/codes/dart/chapter_dynamic_programming/coin_change.dart new file mode 100644 index 000000000..d7e431eba --- /dev/null +++ b/ja/codes/dart/chapter_dynamic_programming/coin_change.dart @@ -0,0 +1,68 @@ +/** + * File: coin_change.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* コイン両替:動的計画法 */ +int coinChangeDP(List coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // dp テーブルを初期化 + List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); + // 状態遷移:先頭行と先頭列 + for (int a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 状態遷移: 残りの行と列 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[i][a] = dp[i - 1][a]; + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1; +} + +/* コイン交換:空間最適化後の動的計画法 */ +int coinChangeDPComp(List coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // dp テーブルを初期化 + List dp = List.filled(amt + 1, MAX); + dp[0] = 0; + // 状態遷移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a]; + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; +} + +/* Driver Code */ +void main() { + List coins = [1, 2, 5]; + int amt = 4; + + // 動的計画法 + int res = coinChangeDP(coins, amt); + print("目標金額を作るのに必要な最小硬貨枚数は $res"); + + // 空間最適化後の動的計画法 + res = coinChangeDPComp(coins, amt); + print("目標金額を作るのに必要な最小硬貨枚数は $res"); +} diff --git a/ja/codes/dart/chapter_dynamic_programming/coin_change_ii.dart b/ja/codes/dart/chapter_dynamic_programming/coin_change_ii.dart new file mode 100644 index 000000000..839309c32 --- /dev/null +++ b/ja/codes/dart/chapter_dynamic_programming/coin_change_ii.dart @@ -0,0 +1,64 @@ +/** + * File: coin_change_ii.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* コイン両替 II:動的計画法 */ +int coinChangeIIDP(List coins, int amt) { + int n = coins.length; + // dp テーブルを初期化 + List> dp = List.generate(n + 1, (index) => List.filled(amt + 1, 0)); + // 先頭列を初期化する + for (int i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 状態遷移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[i][a] = dp[i - 1][a]; + } else { + // コイン i を選ばない場合と選ぶ場合の和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; +} + +/* コイン両替 II:空間最適化した動的計画法 */ +int coinChangeIIDPComp(List coins, int amt) { + int n = coins.length; + // dp テーブルを初期化 + List dp = List.filled(amt + 1, 0); + dp[0] = 1; + // 状態遷移 + for (int i = 1; i <= n; i++) { + for (int a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a]; + } else { + // コイン i を選ばない場合と選ぶ場合の和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; +} + +/* Driver Code */ +void main() { + List coins = [1, 2, 5]; + int amt = 5; + + // 動的計画法 + int res = coinChangeIIDP(coins, amt); + print("目標金額を作る硬貨の組み合わせ数は $res"); + + // 空間最適化後の動的計画法 + res = coinChangeIIDPComp(coins, amt); + print("目標金額を作る硬貨の組み合わせ数は $res"); +} diff --git a/ja/codes/dart/chapter_dynamic_programming/edit_distance.dart b/ja/codes/dart/chapter_dynamic_programming/edit_distance.dart new file mode 100644 index 000000000..280833c79 --- /dev/null +++ b/ja/codes/dart/chapter_dynamic_programming/edit_distance.dart @@ -0,0 +1,125 @@ +/** + * File: edit_distance.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 編集距離:総当たり探索 */ +int editDistanceDFS(String s, String t, int i, int j) { + // s と t がともに空なら 0 を返す + if (i == 0 && j == 0) return 0; + // s が空なら t の長さを返す + if (i == 0) return j; + // t が空なら s の長さを返す + if (j == 0) return i; + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1); + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + int insert = editDistanceDFS(s, t, i, j - 1); + int delete = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // 最小編集回数を返す + return min(min(insert, delete), replace) + 1; +} + +/* 編集距離:メモ化探索 */ +int editDistanceDFSMem(String s, String t, List> mem, int i, int j) { + // s と t がともに空なら 0 を返す + if (i == 0 && j == 0) return 0; + // s が空なら t の長さを返す + if (i == 0) return j; + // t が空なら s の長さを返す + if (j == 0) return i; + // 記録済みなら、それをそのまま返す + if (mem[i][j] != -1) return mem[i][j]; + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + int insert = editDistanceDFSMem(s, t, mem, i, j - 1); + int delete = editDistanceDFSMem(s, t, mem, i - 1, j); + int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 最小編集回数を記録して返す + mem[i][j] = min(min(insert, delete), replace) + 1; + return mem[i][j]; +} + +/* 編集距離:動的計画法 */ +int editDistanceDP(String s, String t) { + int n = s.length, m = t.length; + List> dp = List.generate(n + 1, (_) => List.filled(m + 1, 0)); + // 状態遷移:先頭行と先頭列 + for (int i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 状態遷移: 残りの行と列 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (s[i - 1] == t[j - 1]) { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* 編集距離:空間最適化した動的計画法 */ +int editDistanceDPComp(String s, String t) { + int n = s.length, m = t.length; + List dp = List.filled(m + 1, 0); + // 状態遷移:先頭行 + for (int j = 1; j <= m; j++) { + dp[j] = j; + } + // 状態遷移:残りの行 + for (int i = 1; i <= n; i++) { + // 状態遷移:先頭列 + int leftup = dp[0]; // dp[i-1, j-1] を一時保存する + dp[0] = i; + // 状態遷移:残りの列 + for (int j = 1; j <= m; j++) { + int temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[j] = leftup; + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 次の反復の dp[i-1, j-1] に更新する + } + } + return dp[m]; +} + +/* Driver Code */ +void main() { + String s = "bag"; + String t = "pack"; + int n = s.length, m = t.length; + + // 全探索 + int res = editDistanceDFS(s, t, n, m); + print("" + s + " を " + t + " に変更するには最小で $res 回の編集が必要"); + + // メモ化探索 + List> mem = List.generate(n + 1, (_) => List.filled(m + 1, -1)); + res = editDistanceDFSMem(s, t, mem, n, m); + print("" + s + " を " + t + " に変更するには最小で $res 回の編集が必要"); + + // 動的計画法 + res = editDistanceDP(s, t); + print("" + s + " を " + t + " に変更するには最小で $res 回の編集が必要"); + + // 空間最適化後の動的計画法 + res = editDistanceDPComp(s, t); + print("" + s + " を " + t + " に変更するには最小で $res 回の編集が必要"); +} diff --git a/ja/codes/dart/chapter_dynamic_programming/knapsack.dart b/ja/codes/dart/chapter_dynamic_programming/knapsack.dart new file mode 100644 index 000000000..4bde9549e --- /dev/null +++ b/ja/codes/dart/chapter_dynamic_programming/knapsack.dart @@ -0,0 +1,116 @@ +/** + * File: knapsack.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 0-1 ナップサック:総当たり探索 */ +int knapsackDFS(List wgt, List val, int i, int c) { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if (i == 0 || c == 0) { + return 0; + } + // ナップサック容量を超える場合は、入れない選択しかできない + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + int no = knapsackDFS(wgt, val, i - 1, c); + int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 2つの案のうち価値が大きいほうを返す + return max(no, yes); +} + +/* 0-1 ナップサック:メモ化探索 */ +int knapsackDFSMem( + List wgt, + List val, + List> mem, + int i, + int c, +) { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if (i == 0 || c == 0) { + return 0; + } + // 既に記録があればそのまま返す + if (mem[i][c] != -1) { + return mem[i][c]; + } + // ナップサック容量を超える場合は、入れない選択しかできない + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + int no = knapsackDFSMem(wgt, val, mem, i - 1, c); + int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 2 つの案のうち価値が大きい方を記録して返す + mem[i][c] = max(no, yes); + return mem[i][c]; +} + +/* 0-1 ナップサック:動的計画法 */ +int knapsackDP(List wgt, List val, int cap) { + int n = wgt.length; + // dp テーブルを初期化 + List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); + // 状態遷移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[i][c] = dp[i - 1][c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* 0-1 ナップサック:空間最適化後の動的計画法 */ +int knapsackDPComp(List wgt, List val, int cap) { + int n = wgt.length; + // dp テーブルを初期化 + List dp = List.filled(cap + 1, 0); + // 状態遷移 + for (int i = 1; i <= n; i++) { + // 逆順に走査する + for (int c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +void main() { + List wgt = [10, 20, 30, 40, 50]; + List val = [50, 120, 150, 210, 240]; + int cap = 50; + int n = wgt.length; + + // 全探索 + int res = knapsackDFS(wgt, val, n, cap); + print("ナップサック容量を超えない最大価値は $res"); + + // メモ化探索 + List> mem = + List.generate(n + 1, (index) => List.filled(cap + 1, -1)); + res = knapsackDFSMem(wgt, val, mem, n, cap); + print("ナップサック容量を超えない最大価値は $res"); + + // 動的計画法 + res = knapsackDP(wgt, val, cap); + print("ナップサック容量を超えない最大価値は $res"); + + // 空間最適化後の動的計画法 + res = knapsackDPComp(wgt, val, cap); + print("ナップサック容量を超えない最大価値は $res"); +} diff --git a/ja/codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart b/ja/codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart new file mode 100644 index 000000000..90aad4c15 --- /dev/null +++ b/ja/codes/dart/chapter_dynamic_programming/min_cost_climbing_stairs_dp.dart @@ -0,0 +1,48 @@ +/** + * File: min_cost_climbing_stairs_dp.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 階段登りの最小コスト:動的計画法 */ +int minCostClimbingStairsDP(List cost) { + int n = cost.length - 1; + if (n == 1 || n == 2) return cost[n]; + // 部分問題の解を保存するために dp テーブルを初期化 + List dp = List.filled(n + 1, 0); + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for (int i = 3; i <= n; i++) { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* 階段昇りの最小コスト:空間最適化後の動的計画法 */ +int minCostClimbingStairsDPComp(List cost) { + int n = cost.length - 1; + if (n == 1 || n == 2) return cost[n]; + int a = cost[1], b = cost[2]; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +void main() { + List cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; + print("入力された階段コストのリストは $cost"); + + int res = minCostClimbingStairsDP(cost); + print("階段を登り切る最小コストは $res"); + + res = minCostClimbingStairsDPComp(cost); + print("階段を登り切る最小コストは $res"); +} diff --git a/ja/codes/dart/chapter_dynamic_programming/min_path_sum.dart b/ja/codes/dart/chapter_dynamic_programming/min_path_sum.dart new file mode 100644 index 000000000..c8dd92936 --- /dev/null +++ b/ja/codes/dart/chapter_dynamic_programming/min_path_sum.dart @@ -0,0 +1,120 @@ +/** + * File: min_path_sum.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 最小経路和:全探索 */ +int minPathSumDFS(List> grid, int i, int j) { + // 左上のセルなら探索を終了する + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if (i < 0 || j < 0) { + // Dart では、int 型は固定範囲の整数であり、「無限大」を表す値は存在しない + return BigInt.from(2).pow(31).toInt(); + } + // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // 左上隅から (i, j) までの最小経路コストを返す + return min(left, up) + grid[i][j]; +} + +/* 最小経路和:メモ化探索 */ +int minPathSumDFSMem(List> grid, List> mem, int i, int j) { + // 左上のセルなら探索を終了する + if (i == 0 && j == 0) { + return grid[0][0]; + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if (i < 0 || j < 0) { + // Dart では、int 型は固定範囲の整数であり、「無限大」を表す値は存在しない + return BigInt.from(2).pow(31).toInt(); + } + // 既に記録があればそのまま返す + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左と上のセルからの最小経路コスト + int up = minPathSumDFSMem(grid, mem, i - 1, j); + int left = minPathSumDFSMem(grid, mem, i, j - 1); + // 左上から (i, j) までの最小経路コストを記録して返す + mem[i][j] = min(left, up) + grid[i][j]; + return mem[i][j]; +} + +/* 最小経路和:動的計画法 */ +int minPathSumDP(List> grid) { + int n = grid.length, m = grid[0].length; + // dp テーブルを初期化 + List> dp = List.generate(n, (i) => List.filled(m, 0)); + dp[0][0] = grid[0][0]; + // 状態遷移:先頭行 + for (int j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 状態遷移:先頭列 + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 状態遷移: 残りの行と列 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* 最小経路和:空間最適化後の動的計画法 */ +int minPathSumDPComp(List> grid) { + int n = grid.length, m = grid[0].length; + // dp テーブルを初期化 + List dp = List.filled(m, 0); + dp[0] = grid[0][0]; + for (int j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状態遷移:残りの行 + for (int i = 1; i < n; i++) { + // 状態遷移:先頭列 + dp[0] = dp[0] + grid[i][0]; + // 状態遷移:残りの列 + for (int j = 1; j < m; j++) { + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +void main() { + List> grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], + ]; + int n = grid.length, m = grid[0].length; + +// 全探索 + int res = minPathSumDFS(grid, n - 1, m - 1); + print("左上から右下までの最小経路和は $res"); + +// メモ化探索 + List> mem = List.generate(n, (i) => List.filled(m, -1)); + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + print("左上から右下までの最小経路和は $res"); + +// 動的計画法 + res = minPathSumDP(grid); + print("左上から右下までの最小経路和は $res"); + +// 空間最適化後の動的計画法 + res = minPathSumDPComp(grid); + print("左上から右下までの最小経路和は $res"); +} diff --git a/ja/codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart b/ja/codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart new file mode 100644 index 000000000..38efc5722 --- /dev/null +++ b/ja/codes/dart/chapter_dynamic_programming/unbounded_knapsack.dart @@ -0,0 +1,62 @@ +/** + * File: unbounded_knapsack.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 完全ナップサック問題:動的計画法 */ +int unboundedKnapsackDP(List wgt, List val, int cap) { + int n = wgt.length; + // dp テーブルを初期化 + List> dp = List.generate(n + 1, (index) => List.filled(cap + 1, 0)); + // 状態遷移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[i][c] = dp[i - 1][c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* 完全ナップサック問題:空間最適化後の動的計画法 */ +int unboundedKnapsackDPComp(List wgt, List val, int cap) { + int n = wgt.length; + // dp テーブルを初期化 + List dp = List.filled(cap + 1, 0); + // 状態遷移 + for (int i = 1; i <= n; i++) { + for (int c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[c] = dp[c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +void main() { + List wgt = [1, 2, 3]; + List val = [5, 11, 15]; + int cap = 4; + + // 動的計画法 + int res = unboundedKnapsackDP(wgt, val, cap); + print("ナップサック容量を超えない最大価値は $res"); + + // 空間最適化後の動的計画法 + int resComp = unboundedKnapsackDPComp(wgt, val, cap); + print("ナップサック容量を超えない最大価値は $resComp"); +} diff --git a/ja/codes/dart/chapter_graph/graph_adjacency_list.dart b/ja/codes/dart/chapter_graph/graph_adjacency_list.dart new file mode 100644 index 000000000..2c4350644 --- /dev/null +++ b/ja/codes/dart/chapter_graph/graph_adjacency_list.dart @@ -0,0 +1,124 @@ +/** + * File: graph_adjacency_list.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/vertex.dart'; + +/* 隣接リストに基づく無向グラフクラス */ +class GraphAdjList { + // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 + Map> adjList = {}; + + /* コンストラクタ */ + GraphAdjList(List> edges) { + for (List edge in edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* 頂点数を取得 */ + int size() { + return adjList.length; + } + + /* 辺を追加 */ + void addEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || + !adjList.containsKey(vet2) || + vet1 == vet2) { + throw ArgumentError; + } + // 辺 vet1 - vet2 を追加 + adjList[vet1]!.add(vet2); + adjList[vet2]!.add(vet1); + } + + /* 辺を削除 */ + void removeEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || + !adjList.containsKey(vet2) || + vet1 == vet2) { + throw ArgumentError; + } + // 辺 vet1 - vet2 を削除 + adjList[vet1]!.remove(vet2); + adjList[vet2]!.remove(vet1); + } + + /* 頂点を追加 */ + void addVertex(Vertex vet) { + if (adjList.containsKey(vet)) return; + // 隣接リストに新しいリストを追加 + adjList[vet] = []; + } + + /* 頂点を削除 */ + void removeVertex(Vertex vet) { + if (!adjList.containsKey(vet)) { + throw ArgumentError; + } + // 隣接リストから頂点 vet に対応するリストを削除 + adjList.remove(vet); + // 他の頂点のリストを走査し、vet を含むすべての辺を削除 + adjList.forEach((key, value) { + value.remove(vet); + }); + } + + /* 隣接リストを出力 */ + void printAdjList() { + print("隣接リスト ="); + adjList.forEach((key, value) { + List tmp = []; + for (Vertex vertex in value) { + tmp.add(vertex.val); + } + print("${key.val}: $tmp,"); + }); + } +} + +/* Driver Code */ +void main() { + /* 無向グラフを初期化 */ + List v = Vertex.valsToVets([1, 3, 2, 5, 4]); + List> edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]], + ]; + GraphAdjList graph = GraphAdjList(edges); + print("\n初期化後、グラフは"); + graph.printAdjList(); + + /* 辺を追加 */ + // 頂点 1, 2 は v[0], v[2] + graph.addEdge(v[0], v[2]); + print("\n辺 1-2 を追加後、グラフは"); + graph.printAdjList(); + + /* 辺を削除 */ + // 頂点 1, 3 は v[0], v[1] + graph.removeEdge(v[0], v[1]); + print("\n辺 1-3 を削除後、グラフは"); + graph.printAdjList(); + + /* 頂点を追加 */ + Vertex v5 = Vertex(6); + graph.addVertex(v5); + print("\n頂点 6 を追加後、グラフは"); + graph.printAdjList(); + + /* 頂点を削除 */ + // 頂点 3 は v[1] + graph.removeVertex(v[1]); + print("\n頂点 3 を削除後、グラフは"); + graph.printAdjList(); +} diff --git a/ja/codes/dart/chapter_graph/graph_adjacency_matrix.dart b/ja/codes/dart/chapter_graph/graph_adjacency_matrix.dart new file mode 100644 index 000000000..5a55671de --- /dev/null +++ b/ja/codes/dart/chapter_graph/graph_adjacency_matrix.dart @@ -0,0 +1,133 @@ +/** + * File: graph_adjacency_matrix.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; + +/* 隣接行列に基づく無向グラフクラス */ +class GraphAdjMat { + List vertices = []; // 頂点要素。要素は「頂点値」を表し、インデックスは「頂点インデックス」を表す + List> adjMat = []; // 隣接行列。行・列のインデックスは「頂点インデックス」に対応 + + /* コンストラクタ */ + GraphAdjMat(List vertices, List> edges) { + this.vertices = []; + this.adjMat = []; + // 頂点を追加 + for (int val in vertices) { + addVertex(val); + } + // 辺を追加 + // 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する + for (List e in edges) { + addEdge(e[0], e[1]); + } + } + + /* 頂点数を取得 */ + int size() { + return vertices.length; + } + + /* 頂点を追加 */ + void addVertex(int val) { + int n = size(); + // 頂点リストに新しい頂点の値を追加 + vertices.add(val); + // 隣接行列に 1 行追加 + List newRow = List.filled(n, 0, growable: true); + adjMat.add(newRow); + // 隣接行列に 1 列追加 + for (List row in adjMat) { + row.add(0); + } + } + + /* 頂点を削除 */ + void removeVertex(int index) { + if (index >= size()) { + throw IndexError; + } + // 頂点リストから index の頂点を削除する + vertices.removeAt(index); + // 隣接行列で index 行を削除する + adjMat.removeAt(index); + // 隣接行列で index 列を削除する + for (List row in adjMat) { + row.removeAt(index); + } + } + + /* 辺を追加 */ + // 引数 i, j は vertices の要素インデックスに対応する + void addEdge(int i, int j) { + // インデックスの範囲外と等値の処理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw IndexError; + } + // 無向グラフでは、隣接行列は主対角線に関して対称、すなわち (i, j) == (j, i) を満たす + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* 辺を削除 */ + // 引数 i, j は vertices の要素インデックスに対応する + void removeEdge(int i, int j) { + // インデックスの範囲外と等値の処理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) { + throw IndexError; + } + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* 隣接行列を出力 */ + void printAdjMat() { + print("頂点リスト = $vertices"); + print("隣接行列 = "); + printMatrix(adjMat); + } +} + +/* Driver Code */ +void main() { + /* 無向グラフを初期化 */ + // edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意 + List vertices = [1, 3, 2, 5, 4]; + List> edges = [ + [0, 1], + [0, 3], + [1, 2], + [2, 3], + [2, 4], + [3, 4], + ]; + GraphAdjMat graph = GraphAdjMat(vertices, edges); + print("\n初期化後、グラフは"); + graph.printAdjMat(); + + /* 辺を追加 */ + // 頂点 1, 2 のインデックスはそれぞれ 0, 2 + graph.addEdge(0, 2); + print("\n辺 1-2 を追加後、グラフは"); + graph.printAdjMat(); + + /* 辺を削除 */ + // 頂点 1, 3 のインデックスはそれぞれ 0, 1 + graph.removeEdge(0, 1); + print("\n辺 1-3 を削除後、グラフは"); + graph.printAdjMat(); + + /* 頂点を追加 */ + graph.addVertex(6); + print("\n頂点 6 を追加後、グラフは"); + graph.printAdjMat(); + + /* 頂点を削除 */ + // 頂点 3 のインデックスは 1 + graph.removeVertex(1); + print("\n頂点 3 を削除後、グラフは"); + graph.printAdjMat(); +} diff --git a/ja/codes/dart/chapter_graph/graph_bfs.dart b/ja/codes/dart/chapter_graph/graph_bfs.dart new file mode 100644 index 000000000..dd24ae9b5 --- /dev/null +++ b/ja/codes/dart/chapter_graph/graph_bfs.dart @@ -0,0 +1,66 @@ +/** + * File: graph_bfs.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:collection'; + +import '../utils/vertex.dart'; +import 'graph_adjacency_list.dart'; + +/* 幅優先探索 */ +List graphBFS(GraphAdjList graph, Vertex startVet) { + // 指定した頂点の隣接頂点をすべて取得できるよう、隣接リストでグラフを表現する + // 頂点の走査順序 + List res = []; + // 訪問済み頂点を記録するためのハッシュ集合 + Set visited = {}; + visited.add(startVet); + // BFS の実装にキューを用いる + Queue que = Queue(); + que.add(startVet); + // 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す + while (que.isNotEmpty) { + Vertex vet = que.removeFirst(); // 先頭の頂点をデキュー + res.add(vet); // 訪問した頂点を記録 + // この頂点のすべての隣接頂点を走査 + for (Vertex adjVet in graph.adjList[vet]!) { + if (visited.contains(adjVet)) { + continue; // 訪問済みの頂点をスキップ + } + que.add(adjVet); // 未訪問の頂点のみをキューに追加 + visited.add(adjVet); // この頂点を訪問済みにする + } + } + // 頂点の走査順を返す + return res; +} + +/* Dirver Code */ +void main() { + /* 無向グラフを初期化 */ + List v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + List> edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[1], v[4]], + [v[2], v[5]], + [v[3], v[4]], + [v[3], v[6]], + [v[4], v[5]], + [v[4], v[7]], + [v[5], v[8]], + [v[6], v[7]], + [v[7], v[8]], + ]; + GraphAdjList graph = GraphAdjList(edges); + print("\n初期化後、グラフは"); + graph.printAdjList(); + + /* 幅優先探索 */ + List res = graphBFS(graph, v[0]); + print("\n幅優先探索(BFS)頂点列"); + print(Vertex.vetsToVals(res)); +} diff --git a/ja/codes/dart/chapter_graph/graph_dfs.dart b/ja/codes/dart/chapter_graph/graph_dfs.dart new file mode 100644 index 000000000..512715693 --- /dev/null +++ b/ja/codes/dart/chapter_graph/graph_dfs.dart @@ -0,0 +1,59 @@ +/** + * File: graph_dfs.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/vertex.dart'; +import 'graph_adjacency_list.dart'; + +/* 深さ優先走査の補助関数 */ +void dfs( + GraphAdjList graph, + Set visited, + List res, + Vertex vet, +) { + res.add(vet); // 訪問した頂点を記録 + visited.add(vet); // この頂点を訪問済みにする + // この頂点のすべての隣接頂点を走査 + for (Vertex adjVet in graph.adjList[vet]!) { + if (visited.contains(adjVet)) { + continue; // 訪問済みの頂点をスキップ + } + // 隣接頂点を再帰的に訪問 + dfs(graph, visited, res, adjVet); + } +} + +/* 深さ優先探索 */ +List graphDFS(GraphAdjList graph, Vertex startVet) { + // 頂点の走査順序 + List res = []; + // 訪問済み頂点を記録するためのハッシュ集合 + Set visited = {}; + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +void main() { + /* 無向グラフを初期化 */ + List v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); + List> edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], + ]; + GraphAdjList graph = GraphAdjList(edges); + print("\n初期化後、グラフは"); + graph.printAdjList(); + + /* 深さ優先探索 */ + List res = graphDFS(graph, v[0]); + print("\n深さ優先探索(DFS)頂点列"); + print(Vertex.vetsToVals(res)); +} diff --git a/ja/codes/dart/chapter_greedy/coin_change_greedy.dart b/ja/codes/dart/chapter_greedy/coin_change_greedy.dart new file mode 100644 index 000000000..3a5b4c8f9 --- /dev/null +++ b/ja/codes/dart/chapter_greedy/coin_change_greedy.dart @@ -0,0 +1,50 @@ +/** + * File: coin_change_greedy.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* コイン交換:貪欲法 */ +int coinChangeGreedy(List coins, int amt) { + // coins リストはソート済みと仮定する + int i = coins.length - 1; + int count = 0; + // 残額がなくなるまで貪欲選択を繰り返す + while (amt > 0) { + // 残額以下で最も近い硬貨を見つける + while (i > 0 && coins[i] > amt) { + i--; + } + // coins[i] を選択する + amt -= coins[i]; + count++; + } + // 実行可能な解が見つからなければ -1 を返す + return amt == 0 ? count : -1; +} + +/* Driver Code */ +void main() { + // 貪欲法:大域最適解を保証できる + List coins = [1, 5, 10, 20, 50, 100]; + int amt = 186; + int res = coinChangeGreedy(coins, amt); + print("\ncoins = $coins, amt = $amt"); + print("$amt を作るのに必要な最小硬貨枚数は $res"); + + // 貪欲法:大域最適解を保証できない + coins = [1, 20, 50]; + amt = 60; + res = coinChangeGreedy(coins, amt); + print("\ncoins = $coins, amt = $amt"); + print("$amt を作るのに必要な最小硬貨枚数は $res"); + print("実際に必要な最小枚数は 3 、つまり 20 + 20 + 20"); + + // 貪欲法:大域最適解を保証できない + coins = [1, 49, 50]; + amt = 98; + res = coinChangeGreedy(coins, amt); + print("\ncoins = $coins, amt = $amt"); + print("$amt を作るのに必要な最小硬貨枚数は $res"); + print("実際に必要な最小枚数は 2 、つまり 49 + 49"); +} diff --git a/ja/codes/dart/chapter_greedy/fractional_knapsack.dart b/ja/codes/dart/chapter_greedy/fractional_knapsack.dart new file mode 100644 index 000000000..cfc4aeaf5 --- /dev/null +++ b/ja/codes/dart/chapter_greedy/fractional_knapsack.dart @@ -0,0 +1,47 @@ +/** + * File: fractional_knapsack.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 品物 */ +class Item { + int w; // 品物の重さ + int v; // 品物の価値 + + Item(this.w, this.v); +} + +/* 分数ナップサック:貪欲法 */ +double fractionalKnapsack(List wgt, List val, int cap) { + // 重さと価値の 2 属性を持つ品物リストを作成 + List items = List.generate(wgt.length, (i) => Item(wgt[i], val[i])); + // 単位価値 item.v / item.w の高い順にソートする + items.sort((a, b) => (b.v / b.w).compareTo(a.v / a.w)); + // 貪欲選択を繰り返す + double res = 0; + for (Item item in items) { + if (item.w <= cap) { + // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる + res += item.v; + cap -= item.w; + } else { + // 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる + res += item.v / item.w * cap; + // 残り容量がないため、ループを抜ける + break; + } + } + return res; +} + +/* Driver Code */ +void main() { + List wgt = [10, 20, 30, 40, 50]; + List val = [50, 120, 150, 210, 240]; + int cap = 50; + + // 貪欲法 + double res = fractionalKnapsack(wgt, val, cap); + print("ナップサック容量を超えない最大価値は $res"); +} diff --git a/ja/codes/dart/chapter_greedy/max_capacity.dart b/ja/codes/dart/chapter_greedy/max_capacity.dart new file mode 100644 index 000000000..402a36137 --- /dev/null +++ b/ja/codes/dart/chapter_greedy/max_capacity.dart @@ -0,0 +1,37 @@ +/** + * File: max_capacity.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 最大容量:貪欲法 */ +int maxCapacity(List ht) { + // i, j を初期化し、それぞれ配列の両端に置く + int i = 0, j = ht.length - 1; + // 初期の最大容量は 0 + int res = 0; + // 2 枚の板が出会うまで貪欲選択を繰り返す + while (i < j) { + // 最大容量を更新する + int cap = min(ht[i], ht[j]) * (j - i); + res = max(res, cap); + // 短い方を内側へ動かす + if (ht[i] < ht[j]) { + i++; + } else { + j--; + } + } + return res; +} + +/* Driver Code */ +void main() { + List ht = [3, 8, 5, 2, 7, 7, 3, 4]; + + // 貪欲法 + int res = maxCapacity(ht); + print("最大容量は $res"); +} diff --git a/ja/codes/dart/chapter_greedy/max_product_cutting.dart b/ja/codes/dart/chapter_greedy/max_product_cutting.dart new file mode 100644 index 000000000..3623e4931 --- /dev/null +++ b/ja/codes/dart/chapter_greedy/max_product_cutting.dart @@ -0,0 +1,37 @@ +/** + * File: max_product_cutting.dart + * Created Time: 2023-08-11 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; + +/* 最大切断積:貪欲法 */ +int maxProductCutting(int n) { + // n <= 3 のときは、必ず 1 を切り出す + if (n <= 3) { + return 1 * (n - 1); + } + // 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする + int a = n ~/ 3; + int b = n % 3; + if (b == 1) { + // 余りが 1 のときは、1 * 3 を 2 * 2 に変える + return (pow(3, a - 1) * 2 * 2).toInt(); + } + if (b == 2) { + // 余りが 2 のときは、そのままにする + return (pow(3, a) * 2).toInt(); + } + // 余りが 0 のときは、そのままにする + return pow(3, a).toInt(); +} + +/* Driver Code */ +void main() { + int n = 58; + + // 貪欲法 + int res = maxProductCutting(n); + print("最大分割積は $res"); +} diff --git a/ja/codes/dart/chapter_hashing/array_hash_map.dart b/ja/codes/dart/chapter_hashing/array_hash_map.dart new file mode 100644 index 000000000..e86dea87f --- /dev/null +++ b/ja/codes/dart/chapter_hashing/array_hash_map.dart @@ -0,0 +1,126 @@ +/** + * File: array_hash_map.dart + * Created Time: 2023-03-29 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* キーと値の組 */ +class Pair { + int key; + String val; + Pair(this.key, this.val); +} + +/* 配列ベースのハッシュテーブル */ +class ArrayHashMap { + late List _buckets; + + ArrayHashMap() { + // 100 個のバケットを含む配列を初期化 + _buckets = List.filled(100, null); + } + + /* ハッシュ関数 */ + int _hashFunc(int key) { + final int index = key % 100; + return index; + } + + /* 検索操作 */ + String? get(int key) { + final int index = _hashFunc(key); + final Pair? pair = _buckets[index]; + if (pair == null) { + return null; + } + return pair.val; + } + + /* 追加操作 */ + void put(int key, String val) { + final Pair pair = Pair(key, val); + final int index = _hashFunc(key); + _buckets[index] = pair; + } + + /* 削除操作 */ + void remove(int key) { + final int index = _hashFunc(key); + _buckets[index] = null; + } + + /* すべてのキーと値のペアを取得 */ + List pairSet() { + List pairSet = []; + for (final Pair? pair in _buckets) { + if (pair != null) { + pairSet.add(pair); + } + } + return pairSet; + } + + /* すべてのキーを取得 */ + List keySet() { + List keySet = []; + for (final Pair? pair in _buckets) { + if (pair != null) { + keySet.add(pair.key); + } + } + return keySet; + } + + /* すべての値を取得 */ + List values() { + List valueSet = []; + for (final Pair? pair in _buckets) { + if (pair != null) { + valueSet.add(pair.val); + } + } + return valueSet; + } + + /* ハッシュテーブルを出力 */ + void printHashMap() { + for (final Pair kv in pairSet()) { + print("${kv.key} -> ${kv.val}"); + } + } +} + +/* Driver Code */ +void main() { + /* ハッシュテーブルを初期化 */ + final ArrayHashMap map = ArrayHashMap(); + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.put(12836, "シャオハー"); + map.put(15937, "シャオルオ"); + map.put(16750, "シャオスワン"); + map.put(13276, "シャオファー"); + map.put(10583, "シャオヤー"); + print("\n追加完了後、ハッシュテーブルは\nKey -> Value"); + map.printHashMap(); + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + String? name = map.get(15937); + print("\n学籍番号 15937 を入力すると、名前 $name が見つかりました"); + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + map.remove(10583); + print("\n10583 を削除後、ハッシュテーブルは\nKey -> Value"); + map.printHashMap(); + + /* ハッシュテーブルを走査 */ + print("\nキーと値のペア Key->Value を走査"); + map.pairSet().forEach((kv) => print("${kv.key} -> ${kv.val}")); + print("\nキー Key のみを走査"); + map.keySet().forEach((key) => print("$key")); + print("\n値 Value のみを走査"); + map.values().forEach((val) => print("$val")); +} diff --git a/ja/codes/dart/chapter_hashing/built_in_hash.dart b/ja/codes/dart/chapter_hashing/built_in_hash.dart new file mode 100644 index 000000000..6ed1ef635 --- /dev/null +++ b/ja/codes/dart/chapter_hashing/built_in_hash.dart @@ -0,0 +1,34 @@ +/** + * File: built_in_hash.dart + * Created Time: 2023-06-25 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../chapter_stack_and_queue/linkedlist_deque.dart'; + +/* Driver Code */ +void main() { + int _num = 3; + int hashNum = _num.hashCode; + print("整数 $_num のハッシュ値は $hashNum"); + + bool bol = true; + int hashBol = bol.hashCode; + print("真偽値 $bol のハッシュ値は $hashBol"); + + double dec = 3.14159; + int hashDec = dec.hashCode; + print("小数 $dec のハッシュ値は $hashDec です"); + + String str = "Hello アルゴリズム"; + int hashStr = str.hashCode; + print("文字列 $str のハッシュ値は $hashStr です"); + + List arr = [12836, "シャオハー"]; + int hashArr = arr.hashCode; + print("配列 $arr のハッシュ値は $hashArr です"); + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode; + print("ノードオブジェクト $obj のハッシュ値は $hashObj です"); +} diff --git a/ja/codes/dart/chapter_hashing/hash_map.dart b/ja/codes/dart/chapter_hashing/hash_map.dart new file mode 100644 index 000000000..98feecff8 --- /dev/null +++ b/ja/codes/dart/chapter_hashing/hash_map.dart @@ -0,0 +1,41 @@ +/** + * File: hash_map.dart + * Created Time: 2023-03-29 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* Driver Code */ +void main() { + /* ハッシュテーブルを初期化 */ + final Map map = {}; + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map[12836] = "シャオハー"; + map[15937] = "シャオルオ"; + map[16750] = "シャオスワン"; + map[13276] = "シャオファー"; + map[10583] = "シャオヤー"; + print("\n追加完了後、ハッシュテーブルは\nKey -> Value"); + map.forEach((key, value) => print("$key -> $value")); + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + final String? name = map[15937]; + print("\n学籍番号 15937 を入力すると、名前 $name が見つかりました"); + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + map.remove(10583); + print("\n10583 を削除後、ハッシュテーブルは\nKey -> Value"); + map.forEach((key, value) => print("$key -> $value")); + + /* ハッシュテーブルを走査 */ + print("\nキーと値のペア Key->Value を走査"); + map.forEach((key, value) => print("$key -> $value")); + print("\nキー Key のみを走査"); + map.keys.forEach((key) => print(key)); + print("\n値 Value のみを走査"); + map.forEach((key, value) => print("$value")); + map.values.forEach((value) => print(value)); +} diff --git a/ja/codes/dart/chapter_hashing/hash_map_chaining.dart b/ja/codes/dart/chapter_hashing/hash_map_chaining.dart new file mode 100644 index 000000000..fd9f6611a --- /dev/null +++ b/ja/codes/dart/chapter_hashing/hash_map_chaining.dart @@ -0,0 +1,138 @@ +/** + * File: hash_map_chaining.dart + * Created Time: 2023-06-24 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'array_hash_map.dart'; + +/* チェイン法ハッシュテーブル */ +class HashMapChaining { + late int size; // キーと値のペア数 + late int capacity; // ハッシュテーブル容量 + late double loadThres; // リサイズを発動する負荷率のしきい値 + late int extendRatio; // 拡張倍率 + late List> buckets; // バケット配列 + + /* コンストラクタ */ + HashMapChaining() { + size = 0; + capacity = 4; + loadThres = 2.0 / 3.0; + extendRatio = 2; + buckets = List.generate(capacity, (_) => []); + } + + /* ハッシュ関数 */ + int hashFunc(int key) { + return key % capacity; + } + + /* 負荷率 */ + double loadFactor() { + return size / capacity; + } + + /* 検索操作 */ + String? get(int key) { + int index = hashFunc(key); + List bucket = buckets[index]; + // バケットを走査し、key が見つかれば対応する val を返す + for (Pair pair in bucket) { + if (pair.key == key) { + return pair.val; + } + } + // key が見つからない場合は null を返す + return null; + } + + /* 追加操作 */ + void put(int key, String val) { + // 負荷率がしきい値を超えたら、リサイズを実行 + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + List bucket = buckets[index]; + // バケットを走査し、指定した key が見つかれば対応する val を更新して返す + for (Pair pair in bucket) { + if (pair.key == key) { + pair.val = val; + return; + } + } + // その key が存在しなければ、キーと値のペアを末尾に追加 + Pair pair = Pair(key, val); + bucket.add(pair); + size++; + } + + /* 削除操作 */ + void remove(int key) { + int index = hashFunc(key); + List bucket = buckets[index]; + // バケットを走査してキーと値のペアを削除 + for (Pair pair in bucket) { + if (pair.key == key) { + bucket.remove(pair); + size--; + break; + } + } + } + + /* ハッシュテーブルを拡張 */ + void extend() { + // 元のハッシュテーブルを一時保存 + List> bucketsTmp = buckets; + // リサイズ後の新しいハッシュテーブルを初期化 + capacity *= extendRatio; + buckets = List.generate(capacity, (_) => []); + size = 0; + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す + for (List bucket in bucketsTmp) { + for (Pair pair in bucket) { + put(pair.key, pair.val); + } + } + } + + /* ハッシュテーブルを出力 */ + void printHashMap() { + for (List bucket in buckets) { + List res = []; + for (Pair pair in bucket) { + res.add("${pair.key} -> ${pair.val}"); + } + print(res); + } + } +} + +/* Driver Code */ +void main() { + /* ハッシュテーブルを初期化 */ + HashMapChaining map = HashMapChaining(); + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.put(12836, "シャオハー"); + map.put(15937, "シャオルオ"); + map.put(16750, "シャオスワン"); + map.put(13276, "シャオファー"); + map.put(10583, "シャオヤー"); + print("\n追加完了後、ハッシュテーブルは\nKey -> Value"); + map.printHashMap(); + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + String? name = map.get(13276); + print("\n学籍番号 13276 を入力すると、氏名 ${name} が見つかります"); + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + map.remove(12836); + print("\n12836 を削除した後、ハッシュテーブルは\nKey -> Value"); + map.printHashMap(); +} diff --git a/ja/codes/dart/chapter_hashing/hash_map_open_addressing.dart b/ja/codes/dart/chapter_hashing/hash_map_open_addressing.dart new file mode 100644 index 000000000..7ec83905b --- /dev/null +++ b/ja/codes/dart/chapter_hashing/hash_map_open_addressing.dart @@ -0,0 +1,157 @@ +/** + * File: hash_map_open_addressing.dart + * Created Time: 2023-06-25 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'array_hash_map.dart'; + +/* オープンアドレス法ハッシュテーブル */ +class HashMapOpenAddressing { + late int _size; // キーと値のペア数 + int _capacity = 4; // ハッシュテーブル容量 + double _loadThres = 2.0 / 3.0; // リサイズを発動する負荷率のしきい値 + int _extendRatio = 2; // 拡張倍率 + late List _buckets; // バケット配列 + Pair _TOMBSTONE = Pair(-1, "-1"); // 削除済みマーク + + /* コンストラクタ */ + HashMapOpenAddressing() { + _size = 0; + _buckets = List.generate(_capacity, (index) => null); + } + + /* ハッシュ関数 */ + int hashFunc(int key) { + return key % _capacity; + } + + /* 負荷率 */ + double loadFactor() { + return _size / _capacity; + } + + /* key に対応するバケットインデックスを探す */ + int findBucket(int key) { + int index = hashFunc(key); + int firstTombstone = -1; + // 線形プロービングを行い、空バケットに達したら終了 + while (_buckets[index] != null) { + // key が見つかったら、対応するバケットのインデックスを返す + if (_buckets[index]!.key == key) { + // 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 + if (firstTombstone != -1) { + _buckets[firstTombstone] = _buckets[index]; + _buckets[index] = _TOMBSTONE; + return firstTombstone; // 移動後のバケットインデックスを返す + } + return index; // バケットのインデックスを返す + } + // 最初に見つかった削除マークを記録 + if (firstTombstone == -1 && _buckets[index] == _TOMBSTONE) { + firstTombstone = index; + } + // バケットのインデックスを計算し、末尾を越えたら先頭に戻る + index = (index + 1) % _capacity; + } + // key が存在しない場合は追加位置のインデックスを返す + return firstTombstone == -1 ? index : firstTombstone; + } + + /* 検索操作 */ + String? get(int key) { + // key に対応するバケットインデックスを探す + int index = findBucket(key); + // キーと値の組が見つかったら、対応する val を返す + if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { + return _buckets[index]!.val; + } + // キーと値の組が存在しなければ null を返す + return null; + } + + /* 追加操作 */ + void put(int key, String val) { + // 負荷率がしきい値を超えたら、リサイズを実行 + if (loadFactor() > _loadThres) { + extend(); + } + // key に対応するバケットインデックスを探す + int index = findBucket(key); + // キーと値の組が見つかったら、val を上書きして返す + if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { + _buckets[index]!.val = val; + return; + } + // キーと値の組が存在しない場合は、その組を追加する + _buckets[index] = new Pair(key, val); + _size++; + } + + /* 削除操作 */ + void remove(int key) { + // key に対応するバケットインデックスを探す + int index = findBucket(key); + // キーと値の組が見つかったら、削除マーカーで上書きする + if (_buckets[index] != null && _buckets[index] != _TOMBSTONE) { + _buckets[index] = _TOMBSTONE; + _size--; + } + } + + /* ハッシュテーブルを拡張 */ + void extend() { + // 元のハッシュテーブルを一時保存 + List bucketsTmp = _buckets; + // リサイズ後の新しいハッシュテーブルを初期化 + _capacity *= _extendRatio; + _buckets = List.generate(_capacity, (index) => null); + _size = 0; + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す + for (Pair? pair in bucketsTmp) { + if (pair != null && pair != _TOMBSTONE) { + put(pair.key, pair.val); + } + } + } + + /* ハッシュテーブルを出力 */ + void printHashMap() { + for (Pair? pair in _buckets) { + if (pair == null) { + print("null"); + } else if (pair == _TOMBSTONE) { + print("TOMBSTONE"); + } else { + print("${pair.key} -> ${pair.val}"); + } + } + } +} + +/* Driver Code */ +void main() { + /* ハッシュテーブルを初期化 */ + HashMapOpenAddressing map = HashMapOpenAddressing(); + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.put(12836, "シャオハー"); + map.put(15937, "シャオルオ"); + map.put(16750, "シャオスワン"); + map.put(13276, "シャオファー"); + map.put(10583, "シャオヤー"); + print("\n追加完了後、ハッシュテーブルは\nKey -> Value"); + map.printHashMap(); + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + String? name = map.get(13276); + print("\n学籍番号 13276 を入力すると、氏名 $name が見つかります"); + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + map.remove(16750); + print("\n16750 を削除した後、ハッシュテーブルは\nKey -> Value"); + map.printHashMap(); +} diff --git a/ja/codes/dart/chapter_hashing/simple_hash.dart b/ja/codes/dart/chapter_hashing/simple_hash.dart new file mode 100644 index 000000000..23443cdaf --- /dev/null +++ b/ja/codes/dart/chapter_hashing/simple_hash.dart @@ -0,0 +1,62 @@ +/** + * File: simple_hash.dart + * Created Time: 2023-06-25 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 加算ハッシュ */ +int addHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (int i = 0; i < key.length; i++) { + hash = (hash + key.codeUnitAt(i)) % MODULUS; + } + return hash; +} + +/* 乗算ハッシュ */ +int mulHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (int i = 0; i < key.length; i++) { + hash = (31 * hash + key.codeUnitAt(i)) % MODULUS; + } + return hash; +} + +/* XOR ハッシュ */ +int xorHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (int i = 0; i < key.length; i++) { + hash ^= key.codeUnitAt(i); + } + return hash & MODULUS; +} + +/* 回転ハッシュ */ +int rotHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (int i = 0; i < key.length; i++) { + hash = ((hash << 4) ^ (hash >> 28) ^ key.codeUnitAt(i)) % MODULUS; + } + return hash; +} + +/* Dirver Code */ +void main() { + String key = "Hello アルゴリズム"; + + int hash = addHash(key); + print("加算ハッシュ値は $hash です"); + + hash = mulHash(key); + print("乗算ハッシュ値は $hash です"); + + hash = xorHash(key); + print("XOR ハッシュ値は $hash です"); + + hash = rotHash(key); + print("回転ハッシュ値は $hash です"); +} diff --git a/ja/codes/dart/chapter_heap/my_heap.dart b/ja/codes/dart/chapter_heap/my_heap.dart new file mode 100644 index 000000000..d9e0d58e2 --- /dev/null +++ b/ja/codes/dart/chapter_heap/my_heap.dart @@ -0,0 +1,151 @@ +/** + * File: my_heap.dart + * Created Time: 2023-04-09 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; + +/* 最大ヒープ */ +class MaxHeap { + late List _maxHeap; + + /* コンストラクタ。入力リストに基づいてヒープを構築する */ + MaxHeap(List nums) { + // リスト要素をそのままヒープに追加 + _maxHeap = nums; + // 葉ノード以外のすべてのノードをヒープ化 + for (int i = _parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + + /* 左子ノードのインデックスを取得 */ + int _left(int i) { + return 2 * i + 1; + } + + /* 右子ノードのインデックスを取得 */ + int _right(int i) { + return 2 * i + 2; + } + + /* 親ノードのインデックスを取得 */ + int _parent(int i) { + return (i - 1) ~/ 2; // 切り捨て除算 + } + + /* 要素を交換 */ + void _swap(int i, int j) { + int tmp = _maxHeap[i]; + _maxHeap[i] = _maxHeap[j]; + _maxHeap[j] = tmp; + } + + /* ヒープのサイズを取得 */ + int size() { + return _maxHeap.length; + } + + /* ヒープが空かどうかを判定 */ + bool isEmpty() { + return size() == 0; + } + + /* ヒープ先頭要素にアクセス */ + int peek() { + return _maxHeap[0]; + } + + /* 要素をヒープに追加 */ + void push(int val) { + // ノードを追加 + _maxHeap.add(val); + // 下から上へヒープ化 + siftUp(size() - 1); + } + + /* ノード i から始めて、下から上へヒープ化 */ + void siftUp(int i) { + while (true) { + // ノード i の親ノードを取得 + int p = _parent(i); + // 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 + if (p < 0 || _maxHeap[i] <= _maxHeap[p]) { + break; + } + // 2 つのノードを交換 + _swap(i, p); + // ループで下から上へヒープ化 + i = p; + } + } + + /* 要素をヒープから取り出す */ + int pop() { + // 空判定の処理 + if (isEmpty()) throw Exception('ヒープが空です'); + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + _swap(0, size() - 1); + // ノードを削除 + int val = _maxHeap.removeLast(); + // 上から下へヒープ化 + siftDown(0); + // ヒープ先頭要素を返す + return val; + } + + /* ノード i から始めて、上から下へヒープ化 */ + void siftDown(int i) { + while (true) { + // ノード i, l, r のうち値が最大のノードを ma とする + int l = _left(i); + int r = _right(i); + int ma = i; + if (l < size() && _maxHeap[l] > _maxHeap[ma]) ma = l; + if (r < size() && _maxHeap[r] > _maxHeap[ma]) ma = r; + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + if (ma == i) break; + // 2 つのノードを交換 + _swap(i, ma); + // ループで上から下へヒープ化 + i = ma; + } + } + + /* ヒープ(二分木)を出力 */ + void print() { + printHeap(_maxHeap); + } +} + +/* Driver Code */ +void main() { + /* 最大ヒープを初期化 */ + MaxHeap maxHeap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + print("\nリストを入力してヒープを構築した後"); + maxHeap.print(); + + /* ヒープ頂点の要素を取得 */ + int peek = maxHeap.peek(); + print("\nヒープの先頭要素は $peek です"); + + /* 要素をヒープに追加 */ + int val = 7; + maxHeap.push(val); + print("\n要素 $val をヒープに追加した後"); + maxHeap.print(); + + /* ヒープ頂点の要素を取り出す */ + peek = maxHeap.pop(); + print("\nヒープの先頭要素 $peek を取り出した後"); + maxHeap.print(); + + /* ヒープのサイズを取得 */ + int size = maxHeap.size(); + print("\nヒープ要素数は $size です"); + + /* ヒープが空かどうかを判定 */ + bool isEmpty = maxHeap.isEmpty(); + print("\nヒープが空かどうか $isEmpty"); +} diff --git a/ja/codes/dart/chapter_heap/top_k.dart b/ja/codes/dart/chapter_heap/top_k.dart new file mode 100644 index 000000000..0f300a0ee --- /dev/null +++ b/ja/codes/dart/chapter_heap/top_k.dart @@ -0,0 +1,150 @@ +/** + * File: top_k.dart + * Created Time: 2023-08-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; + +/* ヒープに基づいて配列中の最大の k 個の要素を探す */ +MinHeap topKHeap(List nums, int k) { + // 最小ヒープを初期化し、配列の先頭 k 個の要素をヒープに入れる + MinHeap heap = MinHeap(nums.sublist(0, k)); + // k+1 番目の要素から開始し、ヒープ長を k に保つ + for (int i = k; i < nums.length; i++) { + // 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する + if (nums[i] > heap.peek()) { + heap.pop(); + heap.push(nums[i]); + } + } + return heap; +} + +/* Driver Code */ +void main() { + List nums = [1, 7, 6, 3, 2]; + int k = 3; + + MinHeap res = topKHeap(nums, k); + print("最大の $k 個の要素は"); + res.print(); +} + +/* 最小ヒープ */ +class MinHeap { + late List _minHeap; + + /* コンストラクタ。入力リストに基づいてヒープを構築する */ + MinHeap(List nums) { + // リスト要素をそのままヒープに追加 + _minHeap = nums; + // 葉ノード以外のすべてのノードをヒープ化 + for (int i = _parent(size() - 1); i >= 0; i--) { + siftDown(i); + } + } + + /* ヒープ内の要素を返す */ + List getHeap() { + return _minHeap; + } + + /* 左子ノードのインデックスを取得 */ + int _left(int i) { + return 2 * i + 1; + } + + /* 右子ノードのインデックスを取得 */ + int _right(int i) { + return 2 * i + 2; + } + + /* 親ノードのインデックスを取得 */ + int _parent(int i) { + return (i - 1) ~/ 2; // 切り捨て除算 + } + + /* 要素を交換 */ + void _swap(int i, int j) { + int tmp = _minHeap[i]; + _minHeap[i] = _minHeap[j]; + _minHeap[j] = tmp; + } + + /* ヒープのサイズを取得 */ + int size() { + return _minHeap.length; + } + + /* ヒープが空かどうかを判定 */ + bool isEmpty() { + return size() == 0; + } + + /* ヒープ先頭要素にアクセス */ + int peek() { + return _minHeap[0]; + } + + /* 要素をヒープに追加 */ + void push(int val) { + // ノードを追加 + _minHeap.add(val); + // 下から上へヒープ化 + siftUp(size() - 1); + } + + /* ノード i から始めて、下から上へヒープ化 */ + void siftUp(int i) { + while (true) { + // ノード i の親ノードを取得 + int p = _parent(i); + // 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 + if (p < 0 || _minHeap[i] >= _minHeap[p]) { + break; + } + // 2 つのノードを交換 + _swap(i, p); + // ループで下から上へヒープ化 + i = p; + } + } + + /* 要素をヒープから取り出す */ + int pop() { + // 空判定の処理 + if (isEmpty()) throw Exception('ヒープが空です'); + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + _swap(0, size() - 1); + // ノードを削除 + int val = _minHeap.removeLast(); + // 上から下へヒープ化 + siftDown(0); + // ヒープ先頭要素を返す + return val; + } + + /* ノード i から始めて、上から下へヒープ化 */ + void siftDown(int i) { + while (true) { + // ノード i, l, r のうち値が最大のノードを ma とする + int l = _left(i); + int r = _right(i); + int mi = i; + if (l < size() && _minHeap[l] < _minHeap[mi]) mi = l; + if (r < size() && _minHeap[r] < _minHeap[mi]) mi = r; + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + if (mi == i) break; + // 2 つのノードを交換 + _swap(i, mi); + // ループで上から下へヒープ化 + i = mi; + } + } + + /* ヒープ(二分木)を出力 */ + void print() { + printHeap(_minHeap); + } +} diff --git a/ja/codes/dart/chapter_searching/binary_search.dart b/ja/codes/dart/chapter_searching/binary_search.dart new file mode 100644 index 000000000..8c0f39571 --- /dev/null +++ b/ja/codes/dart/chapter_searching/binary_search.dart @@ -0,0 +1,63 @@ +/** + * File: binary_search.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 二分探索(両閉区間) */ +int binarySearch(List nums, int target) { + // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す + int i = 0, j = nums.length - 1; + // ループし、探索区間が空になったら終了する(i > j で空) + while (i <= j) { + int m = i + (j - i) ~/ 2; // 中点インデックス m を計算 + if (nums[m] < target) { + // この場合、target は区間 [m+1, j] にある + i = m + 1; + } else if (nums[m] > target) { + // この場合、target は区間 [i, m-1] にある + j = m - 1; + } else { + // 目標要素が見つかったらそのインデックスを返す + return m; + } + } + // 目標要素が見つからなければ -1 を返す + return -1; +} + +/* 二分探索(左閉右開区間) */ +int binarySearchLCRO(List nums, int target) { + // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す + int i = 0, j = nums.length; + // ループし、探索区間が空になったら終了する(i = j で空) + while (i < j) { + int m = i + (j - i) ~/ 2; // 中点インデックス m を計算 + if (nums[m] < target) { + // この場合、target は区間 [m+1, j) にある + i = m + 1; + } else if (nums[m] > target) { + // この場合、target は区間 [i, m) にある + j = m; + } else { + // 目標要素が見つかったらそのインデックスを返す + return m; + } + } + // 目標要素が見つからなければ -1 を返す + return -1; +} + +/* Driver Code*/ +void main() { + int target = 6; + final nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + /* 二分探索(両閉区間) */ + int index = binarySearch(nums, target); + print('対象要素 6 のインデックス = $index'); + + /* 二分探索(左閉右開区間) */ + index = binarySearchLCRO(nums, target); + print('対象要素 6 のインデックス = $index'); +} diff --git a/ja/codes/dart/chapter_searching/binary_search_edge.dart b/ja/codes/dart/chapter_searching/binary_search_edge.dart new file mode 100644 index 000000000..e15ac7ad2 --- /dev/null +++ b/ja/codes/dart/chapter_searching/binary_search_edge.dart @@ -0,0 +1,48 @@ +/** + * File: binary_search_edge.dart + * Created Time: 2023-08-14 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'binary_search_insertion.dart'; + +/* 最も左の target を二分探索 */ +int binarySearchLeftEdge(List nums, int target) { + // target の挿入位置を探すのと等価 + int i = binarySearchInsertion(nums, target); + // target が見つからなければ、-1 を返す + if (i == nums.length || nums[i] != target) { + return -1; + } + // target が見つかったら、インデックス i を返す + return i; +} + +/* 最も右の target を二分探索 */ +int binarySearchRightEdge(List nums, int target) { + // 最左の target + 1 を探す問題に変換する + int i = binarySearchInsertion(nums, target + 1); + // j は最も右の target を指し、i は target より大きい最初の要素を指す + int j = i - 1; + // target が見つからなければ、-1 を返す + if (j == -1 || nums[j] != target) { + return -1; + } + // target が見つかったら、インデックス j を返す + return j; +} + +/* Driver Code */ +void main() { + // 重複要素を含む配列 + List nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + print("\n配列 nums = $nums"); + + // 二分探索で左端と右端を探す + for (int target in [6, 7]) { + int index = binarySearchLeftEdge(nums, target); + print("最も左の要素 $target のインデックスは $index です"); + index = binarySearchRightEdge(nums, target); + print("最も右の要素 $target のインデックスは $index です"); + } +} diff --git a/ja/codes/dart/chapter_searching/binary_search_insertion.dart b/ja/codes/dart/chapter_searching/binary_search_insertion.dart new file mode 100644 index 000000000..35c4ca836 --- /dev/null +++ b/ja/codes/dart/chapter_searching/binary_search_insertion.dart @@ -0,0 +1,60 @@ +/** + * File: binary_search_insertion.dart + * Created Time: 2023-08-14 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 二分探索で挿入位置を探す(重複要素なし) */ +int binarySearchInsertionSimple(List nums, int target) { + int i = 0, j = nums.length - 1; // 両閉区間 [0, n-1] を初期化 + while (i <= j) { + int m = i + (j - i) ~/ 2; // 中点インデックス m を計算 + if (nums[m] < target) { + i = m + 1; // target は区間 [m+1, j] にある + } else if (nums[m] > target) { + j = m - 1; // target は区間 [i, m-1] にある + } else { + return m; // target が見つかったら、挿入位置 m を返す + } + } + // target が見つからなければ、挿入位置 i を返す + return i; +} + +/* 二分探索で挿入位置を探す(重複要素あり) */ +int binarySearchInsertion(List nums, int target) { + int i = 0, j = nums.length - 1; // 両閉区間 [0, n-1] を初期化 + while (i <= j) { + int m = i + (j - i) ~/ 2; // 中点インデックス m を計算 + if (nums[m] < target) { + i = m + 1; // target は区間 [m+1, j] にある + } else if (nums[m] > target) { + j = m - 1; // target は区間 [i, m-1] にある + } else { + j = m - 1; // target より小さい最初の要素は区間 [i, m-1] にある + } + } + // 挿入位置 i を返す + return i; +} + +/* Driver Code */ +void main() { + // 重複要素のない配列 + List nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + print("\n配列 nums = $nums"); + // 二分探索で挿入位置を探す + for (int target in [6, 9]) { + int index = binarySearchInsertionSimple(nums, target); + print("要素 $target の挿入位置のインデックスは $index です"); + } + + // 重複要素を含む配列 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + print("\n配列 nums = $nums"); + // 二分探索で挿入位置を探す + for (int target in [2, 6, 20]) { + int index = binarySearchInsertion(nums, target); + print("要素 $target の挿入位置のインデックスは $index です"); + } +} diff --git a/ja/codes/dart/chapter_searching/hashing_search.dart b/ja/codes/dart/chapter_searching/hashing_search.dart new file mode 100644 index 000000000..7598851ba --- /dev/null +++ b/ja/codes/dart/chapter_searching/hashing_search.dart @@ -0,0 +1,54 @@ +/** + * File: hashing_search.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import 'dart:collection'; +import '../utils/list_node.dart'; + +/* ハッシュ探索(配列) */ +int hashingSearchArray(Map map, int target) { + // ハッシュテーブルの key: 目標要素、value: インデックス + // ハッシュテーブルにこの key がなければ -1 を返す + if (!map.containsKey(target)) { + return -1; + } + return map[target]!; +} + +/* ハッシュ探索(連結リスト) */ +ListNode? hashingSearchLinkedList(Map map, int target) { + // ハッシュテーブルの key: 目標ノード値、value: ノードオブジェクト + // ハッシュテーブルにこの key がなければ null を返す + if (!map.containsKey(target)) { + return null; + } + return map[target]!; +} + +/* Driver Code */ +void main() { + int target = 3; + + /* ハッシュ探索(配列) */ + List nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + // ハッシュテーブルを初期化 + Map map = HashMap(); + for (int i = 0; i < nums.length; i++) { + map.putIfAbsent(nums[i], () => i); // key: 要素、value: インデックス + } + int index = hashingSearchArray(map, target); + print('対象要素 3 のインデックス = $index'); + + /* ハッシュ探索(連結リスト) */ + ListNode? head = listToLinkedList(nums); + // ハッシュテーブルを初期化 + Map map1 = HashMap(); + while (head != null) { + map1.putIfAbsent(head.val, () => head!); // key: ノード値、value: ノード + head = head.next; + } + ListNode? node = hashingSearchLinkedList(map1, target); + print('目標ノード値 3 に対応するノードオブジェクトは $node です'); +} diff --git a/ja/codes/dart/chapter_searching/linear_search.dart b/ja/codes/dart/chapter_searching/linear_search.dart new file mode 100644 index 000000000..2366a1c3e --- /dev/null +++ b/ja/codes/dart/chapter_searching/linear_search.dart @@ -0,0 +1,47 @@ +/** + * File: linear_search.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import '../utils/list_node.dart'; + +/* 線形探索(配列) */ +int linearSearchArray(List nums, int target) { + // 配列を走査 + for (int i = 0; i < nums.length; i++) { + // 目標要素が見つかったらそのインデックスを返す + if (nums[i] == target) { + return i; + } + } + // 目標要素が見つからなければ -1 を返す + return -1; +} + +/* 線形探索(連結リスト) */ +ListNode? linearSearchList(ListNode? head, int target) { + // 連結リストを走査 + while (head != null) { + // 対象ノードが見つかったら、それを返す + if (head.val == target) return head; + head = head.next; + } + // 対象要素が見つからない場合は `null` を返す + return null; +} + +/* Driver Code */ +void main() { + int target = 3; + + /* 配列で線形探索を行う */ + List nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + int index = linearSearchArray(nums, target); + print('対象要素 3 のインデックス = $index'); + + /* 連結リストで線形探索を行う */ + ListNode? head = listToLinkedList(nums); + ListNode? node = linearSearchList(head, target); + print('目標ノード値 3 に対応するノードオブジェクトは $node です'); +} diff --git a/ja/codes/dart/chapter_searching/two_sum.dart b/ja/codes/dart/chapter_searching/two_sum.dart new file mode 100644 index 000000000..4752be47d --- /dev/null +++ b/ja/codes/dart/chapter_searching/two_sum.dart @@ -0,0 +1,49 @@ +/** + * File: two_sum.dart + * Created Time: 2023-2-11 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import 'dart:collection'; + +/* 方法1: 総当たり列挙 */ +List twoSumBruteForce(List nums, int target) { + int size = nums.length; + // 2重ループのため、時間計算量は O(n^2) + for (var i = 0; i < size - 1; i++) { + for (var j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) return [i, j]; + } + } + return [0]; +} + +/* 方法2: 補助ハッシュテーブル */ +List twoSumHashTable(List nums, int target) { + int size = nums.length; + // 補助ハッシュテーブルを使用し、空間計算量は O(n) + Map dic = HashMap(); + // 単一ループで、時間計算量は O(n) + for (var i = 0; i < size; i++) { + if (dic.containsKey(target - nums[i])) { + return [dic[target - nums[i]]!, i]; + } + dic.putIfAbsent(nums[i], () => i); + } + return [0]; +} + +/* Driver Code */ +void main() { + // ======= Test Case ======= + List nums = [2, 7, 11, 15]; + int target = 13; + + // ====== Driver Code ====== + // 方法 1 + List res = twoSumBruteForce(nums, target); + print('方法1 res = $res'); + // 方法 2 + res = twoSumHashTable(nums, target); + print('方法2 res = $res'); +} diff --git a/ja/codes/dart/chapter_sorting/bubble_sort.dart b/ja/codes/dart/chapter_sorting/bubble_sort.dart new file mode 100644 index 000000000..5935877b0 --- /dev/null +++ b/ja/codes/dart/chapter_sorting/bubble_sort.dart @@ -0,0 +1,51 @@ +/** + * File: bubble_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* バブルソート */ +void bubbleSort(List nums) { + // 外側のループ:未ソート区間は [0, i] + for (int i = nums.length - 1; i > 0; i--) { + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // nums[j] と nums[j + 1] を交換 + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } +} + +/* バブルソート(フラグ最適化) */ +void bubbleSortWithFlag(List nums) { + // 外側のループ:未ソート区間は [0, i] + for (int i = nums.length - 1; i > 0; i--) { + bool flag = false; // フラグを初期化する + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for (int j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // nums[j] と nums[j + 1] を交換 + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; // 交換する要素を記録 + } + } + if (!flag) break; // このバブル処理で要素交換が一度もなければそのまま終了 + } +} + +/* Driver Code */ +void main() { + List nums = [4, 1, 3, 1, 5, 2]; + bubbleSort(nums); + print("バブルソート完了後 nums = $nums"); + + List nums1 = [4, 1, 3, 1, 5, 2]; + bubbleSortWithFlag(nums1); + print("バブルソート完了後 nums1 = $nums1"); +} diff --git a/ja/codes/dart/chapter_sorting/bucket_sort.dart b/ja/codes/dart/chapter_sorting/bucket_sort.dart new file mode 100644 index 000000000..b9ad290cf --- /dev/null +++ b/ja/codes/dart/chapter_sorting/bucket_sort.dart @@ -0,0 +1,39 @@ +/** + * File: bucket_sort.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* バケットソート */ +void bucketSort(List nums) { + // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする + int k = nums.length ~/ 2; + List> buckets = List.generate(k, (index) => []); + + // 1. 配列要素を各バケットに振り分ける + for (double _num in nums) { + // 入力データの範囲は [0, 1) であり、_num * k を用いてインデックス範囲 [0, k-1] に写像する + int i = (_num * k).toInt(); + // _num をバケット bucket_idx に追加 + buckets[i].add(_num); + } + // 2. 各バケットをソートする + for (List bucket in buckets) { + bucket.sort(); + } + // 3. バケットを走査して結果を結合 + int i = 0; + for (List bucket in buckets) { + for (double _num in bucket) { + nums[i++] = _num; + } + } +} + +/* Driver Code*/ +void main() { + // 入力データは範囲 [0, 1) の浮動小数点数とする + final nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; + bucketSort(nums); + print('バケットソート完了後 nums = $nums'); +} diff --git a/ja/codes/dart/chapter_sorting/counting_sort.dart b/ja/codes/dart/chapter_sorting/counting_sort.dart new file mode 100644 index 000000000..4a5df661f --- /dev/null +++ b/ja/codes/dart/chapter_sorting/counting_sort.dart @@ -0,0 +1,72 @@ +/** + * File: counting_sort.dart + * Created Time: 2023-05-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ +import 'dart:math'; + +/* 計数ソート */ +// 簡易実装のため、オブジェクトのソートには使えない +void countingSortNaive(List nums) { + // 1. 配列の最大要素 m を求める + int m = 0; + for (int _num in nums) { + m = max(m, _num); + } + // 2. 各数値の出現回数を数える + // counter[_num] は _num の出現回数を表す + List counter = List.filled(m + 1, 0); + for (int _num in nums) { + counter[_num]++; + } + // 3. counter を走査し、各要素を元の配列 nums に書き戻す + int i = 0; + for (int _num = 0; _num < m + 1; _num++) { + for (int j = 0; j < counter[_num]; j++, i++) { + nums[i] = _num; + } + } +} + +/* 計数ソート */ +// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである +void countingSort(List nums) { + // 1. 配列の最大要素 m を求める + int m = 0; + for (int _num in nums) { + m = max(m, _num); + } + // 2. 各数値の出現回数を数える + // counter[_num] は _num の出現回数を表す + List counter = List.filled(m + 1, 0); + for (int _num in nums) { + counter[_num]++; + } + // 3. counter の累積和を求め、「出現回数」を「末尾インデックス」に変換する + // つまり counter[_num]-1 は、res において _num が最後に出現する位置のインデックスである + for (int i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. nums を逆順に走査し、各要素を結果配列 res に格納する + // 結果を記録するための配列 res を初期化 + int n = nums.length; + List res = List.filled(n, 0); + for (int i = n - 1; i >= 0; i--) { + int _num = nums[i]; + res[counter[_num] - 1] = _num; // _num を対応する添字に配置 + counter[_num]--; // 累積和を 1 減らし、次に _num を配置するインデックスを得る + } + // 結果配列 res で元の配列 nums を上書きする + nums.setAll(0, res); +} + +/* Driver Code*/ +void main() { + final nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + countingSortNaive(nums); + print('カウントソート(オブジェクトはソート不可)完了後 nums = $nums'); + + final nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + countingSort(nums1); + print('カウントソート完了後 nums1 = $nums1'); +} diff --git a/ja/codes/dart/chapter_sorting/heap_sort.dart b/ja/codes/dart/chapter_sorting/heap_sort.dart new file mode 100644 index 000000000..9aa14ff8e --- /dev/null +++ b/ja/codes/dart/chapter_sorting/heap_sort.dart @@ -0,0 +1,49 @@ +/** + * File: heap_sort.dart + * Created Time: 2023-06-01 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* ヒープの長さは n。ノード i から下方向にヒープ化 */ +void siftDown(List nums, int n, int i) { + while (true) { + // ノード i, l, r のうち値が最大のノードを ma とする + int l = 2 * i + 1; + int r = 2 * i + 2; + int ma = i; + if (l < n && nums[l] > nums[ma]) ma = l; + if (r < n && nums[r] > nums[ma]) ma = r; + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + if (ma == i) break; + // 2 つのノードを交換 + int temp = nums[i]; + nums[i] = nums[ma]; + nums[ma] = temp; + // ループで上から下へヒープ化 + i = ma; + } +} + +/* ヒープソート */ +void heapSort(List nums) { + // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する + for (int i = nums.length ~/ 2 - 1; i >= 0; i--) { + siftDown(nums, nums.length, i); + } + // ヒープから最大要素を取り出し、n-1 回繰り返す + for (int i = nums.length - 1; i > 0; i--) { + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + int tmp = nums[0]; + nums[0] = nums[i]; + nums[i] = tmp; + // 根ノードを起点に、上から下へヒープ化 + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +void main() { + List nums = [4, 1, 3, 1, 5, 2]; + heapSort(nums); + print("ヒープソート完了後 nums = $nums"); +} diff --git a/ja/codes/dart/chapter_sorting/insertion_sort.dart b/ja/codes/dart/chapter_sorting/insertion_sort.dart new file mode 100644 index 000000000..7a19b25e8 --- /dev/null +++ b/ja/codes/dart/chapter_sorting/insertion_sort.dart @@ -0,0 +1,26 @@ +/** + * File: insertion_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* 挿入ソート */ +void insertionSort(List nums) { + // 外側ループ:整列済み区間は [0, i-1] + for (int i = 1; i < nums.length; i++) { + int base = nums[i], j = i - 1; + // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // nums[j] を 1 つ右へ移動する + j--; + } + nums[j + 1] = base; // base を正しい位置に配置する + } +} + +/* Driver Code */ +void main() { + List nums = [4, 1, 3, 1, 5, 2]; + insertionSort(nums); + print("挿入ソート完了後 nums = $nums"); +} diff --git a/ja/codes/dart/chapter_sorting/merge_sort.dart b/ja/codes/dart/chapter_sorting/merge_sort.dart new file mode 100644 index 000000000..5cfe0cd86 --- /dev/null +++ b/ja/codes/dart/chapter_sorting/merge_sort.dart @@ -0,0 +1,52 @@ +/** + * File: merge_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* 左部分配列と右部分配列をマージ */ +void merge(List nums, int left, int mid, int right) { + // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] + // マージ結果を格納する一時配列 tmp を作成 + List tmp = List.filled(right - left + 1, 0); + // 左右の部分配列の開始インデックスを初期化する + int i = left, j = mid + 1, k = 0; + // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) + tmp[k++] = nums[i++]; + else + tmp[k++] = nums[j++]; + } + // 左右の部分配列の残り要素を一時配列にコピーする + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする + for (k = 0; k < tmp.length; k++) { + nums[left + k] = tmp[k]; + } +} + +/* マージソート */ +void mergeSort(List nums, int left, int right) { + // 終了条件 + if (left >= right) return; // 部分配列の長さが 1 になったら再帰を終了 + // 分割フェーズ + int mid = left + (right - left) ~/ 2; // 中点を計算 + mergeSort(nums, left, mid); // 左部分配列を再帰処理 + mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理 + // マージフェーズ + merge(nums, left, mid, right); +} + +/* Driver Code */ +void main() { + /* マージソート */ + List nums = [7, 3, 2, 6, 0, 1, 5, 4]; + mergeSort(nums, 0, nums.length - 1); + print("マージソート完了後 nums = $nums"); +} diff --git a/ja/codes/dart/chapter_sorting/quick_sort.dart b/ja/codes/dart/chapter_sorting/quick_sort.dart new file mode 100644 index 000000000..8bc1f0842 --- /dev/null +++ b/ja/codes/dart/chapter_sorting/quick_sort.dart @@ -0,0 +1,145 @@ +/** + * File: quick_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* クイックソートクラス */ +class QuickSort { + /* 要素の交換 */ + static void _swap(List nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 番兵分割 */ + static int _partition(List nums, int left, int right) { + // nums[left] を基準値とする + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) j--; // 右から左へ基準値未満の最初の要素を探す + while (i < j && nums[i] <= nums[left]) i++; // 左から右へ基準値より大きい最初の要素を探す + _swap(nums, i, j); // この 2 つの要素を交換 + } + _swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する + return i; // 基準値のインデックスを返す + } + + /* クイックソート */ + static void quickSort(List nums, int left, int right) { + // 部分配列の長さが 1 なら再帰を終了する + if (left >= right) return; + // 番兵分割 + int pivot = _partition(nums, left, right); + // 左右の部分配列を再帰処理 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +} + +/* クイックソートクラス(中央値ピボット最適化) */ +class QuickSortMedian { + /* 要素の交換 */ + static void _swap(List nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 3つの候補要素の中央値を選ぶ */ + static int _medianThree(List nums, int left, int mid, int right) { + int l = nums[left], m = nums[mid], r = nums[right]; + if ((l <= m && m <= r) || (r <= m && m <= l)) + return mid; // m は l と r の間 + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // l は m と r の間 + return right; + } + + /* 番兵による分割処理(3 点中央値) */ + static int _partition(List nums, int left, int right) { + // 3つの候補要素の中央値を選ぶ + int med = _medianThree(nums, left, (left + right) ~/ 2, right); + // 中央値を配列の最左端に交換する + _swap(nums, left, med); + // nums[left] を基準値とする + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) j--; // 右から左へ基準値未満の最初の要素を探す + while (i < j && nums[i] <= nums[left]) i++; // 左から右へ基準値より大きい最初の要素を探す + _swap(nums, i, j); // この 2 つの要素を交換 + } + _swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する + return i; // 基準値のインデックスを返す + } + + /* クイックソート */ + static void quickSort(List nums, int left, int right) { + // 部分配列の長さが 1 なら再帰を終了する + if (left >= right) return; + // 番兵分割 + int pivot = _partition(nums, left, right); + // 左右の部分配列を再帰処理 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +} + +/* クイックソートクラス(再帰深度最適化) */ +class QuickSortTailCall { + /* 要素の交換 */ + static void _swap(List nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 番兵分割 */ + static int _partition(List nums, int left, int right) { + // nums[left] を基準値とする + int i = left, j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) j--; // 右から左へ基準値未満の最初の要素を探す + while (i < j && nums[i] <= nums[left]) i++; // 左から右へ基準値より大きい最初の要素を探す + _swap(nums, i, j); // この 2 つの要素を交換 + } + _swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する + return i; // 基準値のインデックスを返す + } + + /* クイックソート(再帰深度最適化) */ + static void quickSort(List nums, int left, int right) { + // 部分配列の長さが 1 なら終了 + while (left < right) { + // 番兵による分割処理 + int pivot = _partition(nums, left, right); + // 2 つの部分配列のうち短いほうにクイックソートを適用する + if (pivot - left < right - pivot) { + quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソート + left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソート + right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1] + } + } + } +} + +/* Driver Code */ +void main() { + /* クイックソート */ + List nums = [2, 4, 1, 0, 3, 5]; + QuickSort.quickSort(nums, 0, nums.length - 1); + print("クイックソート完了後 nums = $nums"); + + /* クイックソート(中央値の基準値で最適化) */ + List nums1 = [2, 4, 1, 0, 3, 5]; + QuickSortMedian.quickSort(nums1, 0, nums1.length - 1); + print("クイックソート(中央値ピボット最適化)完了後 nums1 = $nums1"); + + /* クイックソート(再帰深度最適化) */ + List nums2 = [2, 4, 1, 0, 3, 5]; + QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1); + print("クイックソート(再帰深度最適化)完了後 nums2 = $nums2"); +} diff --git a/ja/codes/dart/chapter_sorting/radix_sort.dart b/ja/codes/dart/chapter_sorting/radix_sort.dart new file mode 100644 index 000000000..ef447c8e5 --- /dev/null +++ b/ja/codes/dart/chapter_sorting/radix_sort.dart @@ -0,0 +1,71 @@ +/** + * File: radix_sort.dart + * Created Time: 2023-02-14 + * Author: what-is-me (whatisme@outlook.jp) + */ + +/* 要素 `_num` の第 k 桁を取得する。ここで `exp = 10^(k-1)` */ +int digit(int _num, int exp) { + // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す + return (_num ~/ exp) % 10; +} + +/* 計数ソート(nums の k 桁目でソート) */ +void countingSortDigit(List nums, int exp) { + // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 + List counter = List.filled(10, 0); + int n = nums.length; + // 0~9 の各数字の出現回数を集計する + for (int i = 0; i < n; i++) { + int d = digit(nums[i], exp); // nums[i] の第 k 位を取得し、d とする + counter[d]++; // 数字 d の出現回数を数える + } + // 累積和を求め、「出現回数」を「配列インデックス」に変換する + for (int i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する + List res = List.filled(n, 0); + for (int i = n - 1; i >= 0; i--) { + int d = digit(nums[i], exp); + int j = counter[d] - 1; // d の配列内インデックス j を取得する + res[j] = nums[i]; // 現在の要素をインデックス j に格納する + counter[d]--; // d の個数を 1 減らす + } + // 結果で元の配列 nums を上書きする + for (int i = 0; i < n; i++) nums[i] = res[i]; +} + +/* 基数ソート */ +void radixSort(List nums) { + // 最大桁数の判定用に配列の最大要素を取得する + // dart の `int` の長さは 64 ビット + int m = -1 << 63; + for (int _num in nums) if (_num > m) m = _num; + // 下位桁から上位桁の順に走査する + for (int exp = 1; exp <= m; exp *= 10) + // 配列要素の k 桁目に対して計数ソートを行う + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // つまり exp = 10^(k-1) + countingSortDigit(nums, exp); +} + +/* Driver Code */ +void main() { + // 基数ソート + List nums = [ + 10546151, + 35663510, + 42865989, + 34862445, + 81883077, + 88906420, + 72429244, + 30524779, + 82060337, + 63832996 + ]; + radixSort(nums); + print("基数ソート完了後 nums = $nums"); +} diff --git a/ja/codes/dart/chapter_sorting/selection_sort.dart b/ja/codes/dart/chapter_sorting/selection_sort.dart new file mode 100644 index 000000000..32d289200 --- /dev/null +++ b/ja/codes/dart/chapter_sorting/selection_sort.dart @@ -0,0 +1,29 @@ +/** + * File: selection_sort.dart + * Created Time: 2023-06-01 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 選択ソート */ +void selectionSort(List nums) { + int n = nums.length; + // 外側ループ:未整列区間は [i, n-1] + for (int i = 0; i < n - 1; i++) { + // 内側のループ:未ソート区間の最小要素を見つける + int k = i; + for (int j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) k = j; // 最小要素のインデックスを記録 + } + // その最小要素を未整列区間の先頭要素と交換する + int temp = nums[i]; + nums[i] = nums[k]; + nums[k] = temp; + } +} + +/* Driver Code */ +void main() { + List nums = [4, 1, 3, 1, 5, 2]; + selectionSort(nums); + print("選択ソート完了後 nums = $nums"); +} diff --git a/ja/codes/dart/chapter_stack_and_queue/array_deque.dart b/ja/codes/dart/chapter_stack_and_queue/array_deque.dart new file mode 100644 index 000000000..3ca660bef --- /dev/null +++ b/ja/codes/dart/chapter_stack_and_queue/array_deque.dart @@ -0,0 +1,146 @@ +/** + * File: array_deque.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 循環配列ベースの両端キュー */ +class ArrayDeque { + late List _nums; // 両端キューの要素を格納する配列 + late int _front; // 先頭ポインタ。先頭要素を指す + late int _queSize; // 両端キューの長さ + + /* コンストラクタ */ + ArrayDeque(int capacity) { + this._nums = List.filled(capacity, 0); + this._front = this._queSize = 0; + } + + /* 両端キューの容量を取得 */ + int capacity() { + return _nums.length; + } + + /* 両端キューの長さを取得 */ + int size() { + return _queSize; + } + + /* 両端キューが空かどうかを判定 */ + bool isEmpty() { + return _queSize == 0; + } + + /* 循環配列のインデックスを計算 */ + int index(int i) { + // 剰余演算により配列の先頭と末尾をつなげる + // i が配列の末尾を越えたら先頭に戻る + // i が配列の先頭を越えて前に出たら末尾に戻る + return (i + capacity()) % capacity(); + } + + /* キュー先頭にエンキュー */ + void pushFirst(int _num) { + if (_queSize == capacity()) { + throw Exception("両端キューがいっぱいです"); + } + // 先頭ポインタを左に 1 つ移動する + // 剰余演算により _front が配列の先頭を越えたあと末尾に戻るようにする + _front = index(_front - 1); + // _num をキューの先頭に追加 + _nums[_front] = _num; + _queSize++; + } + + /* キュー末尾にエンキュー */ + void pushLast(int _num) { + if (_queSize == capacity()) { + throw Exception("両端キューがいっぱいです"); + } + // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す + int rear = index(_front + _queSize); + // _num をキュー末尾に追加 + _nums[rear] = _num; + _queSize++; + } + + /* キュー先頭からデキュー */ + int popFirst() { + int _num = peekFirst(); + // 先頭ポインタを右に 1 つ移動する + _front = index(_front + 1); + _queSize--; + return _num; + } + + /* キュー末尾からデキュー */ + int popLast() { + int _num = peekLast(); + _queSize--; + return _num; + } + + /* キュー先頭の要素にアクセス */ + int peekFirst() { + if (isEmpty()) { + throw Exception("両端キューが空です"); + } + return _nums[_front]; + } + + /* キュー末尾の要素にアクセス */ + int peekLast() { + if (isEmpty()) { + throw Exception("両端キューが空です"); + } + // 末尾要素のインデックスを計算 + int last = index(_front + _queSize - 1); + return _nums[last]; + } + + /* 出力用の配列を返す */ + List toArray() { + // 有効長の範囲内のリスト要素のみを変換 + List res = List.filled(_queSize, 0); + for (int i = 0, j = _front; i < _queSize; i++, j++) { + res[i] = _nums[index(j)]; + } + return res; + } +} + +/* Driver Code */ +void main() { + /* 両端キューを初期化 */ + final ArrayDeque deque = ArrayDeque(10); + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + print("両端キュー deque = ${deque.toArray()}"); + + /* 要素にアクセス */ + final int peekFirst = deque.peekFirst(); + print("先頭要素 peekFirst = $peekFirst"); + final int peekLast = deque.peekLast(); + print("末尾要素 peekLast = $peekLast"); + + /* 要素をエンキュー */ + deque.pushLast(4); + print("要素 4 を末尾に追加した後 deque = ${deque.toArray()}"); + deque.pushFirst(1); + print("要素 1 を先頭に追加した後 deque = ${deque.toArray()}"); + + /* 要素をデキュー */ + final int popLast = deque.popLast(); + print("末尾から取り出した要素 = $popLast ,末尾から取り出した後 deque = ${deque.toArray()}"); + final int popFirst = deque.popFirst(); + print("先頭から取り出した要素 = $popFirst ,先頭から取り出した後 deque = ${deque.toArray()}"); + + /* 両端キューの長さを取得 */ + final int size = deque.size(); + print("両端キューの長さ size = $size"); + + /* 両端キューが空かどうかを判定 */ + final bool isEmpty = deque.isEmpty(); + print("両端キューが空かどうか = $isEmpty"); +} diff --git a/ja/codes/dart/chapter_stack_and_queue/array_queue.dart b/ja/codes/dart/chapter_stack_and_queue/array_queue.dart new file mode 100644 index 000000000..af9e3bfa7 --- /dev/null +++ b/ja/codes/dart/chapter_stack_and_queue/array_queue.dart @@ -0,0 +1,110 @@ +/** + * File: array_queue.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 循環配列ベースのキュー */ +class ArrayQueue { + late List _nums; // キュー要素を格納する配列 + late int _front; // 先頭ポインタ。先頭要素を指す + late int _queSize; // キューの長さ + + ArrayQueue(int capacity) { + _nums = List.filled(capacity, 0); + _front = _queSize = 0; + } + + /* キューの容量を取得 */ + int capaCity() { + return _nums.length; + } + + /* キューの長さを取得 */ + int size() { + return _queSize; + } + + /* キューが空かどうかを判定 */ + bool isEmpty() { + return _queSize == 0; + } + + /* エンキュー */ + void push(int _num) { + if (_queSize == capaCity()) { + throw Exception("キューは満杯です"); + } + // 末尾ポインタを計算し、末尾インデックス + 1 を指す + // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする + int rear = (_front + _queSize) % capaCity(); + // _num をキュー末尾に追加 + _nums[rear] = _num; + _queSize++; + } + + /* デキュー */ + int pop() { + int _num = peek(); + // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す + _front = (_front + 1) % capaCity(); + _queSize--; + return _num; + } + + /* キュー先頭の要素にアクセス */ + int peek() { + if (isEmpty()) { + throw Exception("キューが空です"); + } + return _nums[_front]; + } + + /* Array を返す */ + List toArray() { + // 有効長の範囲内のリスト要素のみを変換 + final List res = List.filled(_queSize, 0); + for (int i = 0, j = _front; i < _queSize; i++, j++) { + res[i] = _nums[j % capaCity()]; + } + return res; + } +} + +/* Driver Code */ +void main() { + /* キューを初期化 */ + final int capacity = 10; + final ArrayQueue queue = ArrayQueue(capacity); + + /* 要素をエンキュー */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + print("キュー queue = ${queue.toArray()}"); + + /* キュー先頭の要素にアクセス */ + final int peek = queue.peek(); + print("先頭要素 peek = $peek"); + + /* 要素をデキュー */ + final int pop = queue.pop(); + print("デキューした要素 pop = $pop ,デキュー後の queue = ${queue.toArray()}"); + + /* キューの長さを取得 */ + final int size = queue.size(); + print("キューの長さ size = $size"); + + /* キューが空かどうかを判定 */ + final bool isEmpty = queue.isEmpty(); + print("キューが空かどうか = $isEmpty"); + + /* 循環配列をテストする */ + for (int i = 0; i < 10; i++) { + queue.push(i); + queue.pop(); + print("$i 回目のエンキュー + デキュー後の queue = ${queue.toArray()}"); + } +} diff --git a/ja/codes/dart/chapter_stack_and_queue/array_stack.dart b/ja/codes/dart/chapter_stack_and_queue/array_stack.dart new file mode 100644 index 000000000..dc80dc3d4 --- /dev/null +++ b/ja/codes/dart/chapter_stack_and_queue/array_stack.dart @@ -0,0 +1,77 @@ +/** + * File: array_stack.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 配列ベースのスタック */ +class ArrayStack { + late List _stack; + ArrayStack() { + _stack = []; + } + + /* スタックの長さを取得 */ + int size() { + return _stack.length; + } + + /* スタックが空かどうかを判定 */ + bool isEmpty() { + return _stack.isEmpty; + } + + /* プッシュ */ + void push(int _num) { + _stack.add(_num); + } + + /* ポップ */ + int pop() { + if (isEmpty()) { + throw Exception("スタックが空です"); + } + return _stack.removeLast(); + } + + /* スタックトップの要素にアクセス */ + int peek() { + if (isEmpty()) { + throw Exception("スタックが空です"); + } + return _stack.last; + } + + /* スタックを Array に変換して返す */ + List toArray() => _stack; +} + +/* Driver Code */ +void main() { + /* スタックを初期化 */ + final ArrayStack stack = ArrayStack(); + + /* 要素をプッシュ */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print("スタック stack = ${stack.toArray()}"); + + /* スタックトップの要素にアクセス */ + final int peek = stack.peek(); + print("スタックトップの要素 peek = $peek"); + + /* 要素をポップ */ + final int pop = stack.pop(); + print("ポップした要素 pop = $pop ,ポップ後の stack = ${stack.toArray()}"); + + /* スタックの長さを取得 */ + final int size = stack.size(); + print("スタックの長さ size = $size"); + + /* 空かどうかを判定 */ + final bool isEmpty = stack.isEmpty(); + print("スタックが空かどうか = $isEmpty"); +} diff --git a/ja/codes/dart/chapter_stack_and_queue/deque.dart b/ja/codes/dart/chapter_stack_and_queue/deque.dart new file mode 100644 index 000000000..9b9b6ef17 --- /dev/null +++ b/ja/codes/dart/chapter_stack_and_queue/deque.dart @@ -0,0 +1,42 @@ +/** + * File: deque.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:collection'; + +void main() { + /* 両端キューを初期化 */ + final Queue deque = Queue(); + deque.addFirst(3); + deque.addLast(2); + deque.addLast(5); + print("両端キュー deque = $deque"); + + /* 要素にアクセス */ + final int peekFirst = deque.first; + print("先頭要素 peekFirst = $peekFirst"); + final int peekLast = deque.last; + print("末尾要素 peekLast = $peekLast"); + + /* 要素をエンキュー */ + deque.addLast(4); + print("要素 4 を末尾にエンキューした後の deque = $deque"); + deque.addFirst(1); + print("要素 1 を先頭にエンキューした後の deque = $deque"); + + /* 要素をデキュー */ + final int popLast = deque.removeLast(); + print("末尾からデキューした要素 = $popLast ,末尾からデキュー後の deque = $deque"); + final int popFirst = deque.removeFirst(); + print("先頭からデキューした要素 = $popFirst ,先頭からデキュー後の deque = $deque"); + + /* 両端キューの長さを取得 */ + final int size = deque.length; + print("両端キューの長さ size = $size"); + + /* 両端キューが空かどうかを判定 */ + final bool isEmpty = deque.isEmpty; + print("両端キューが空かどうか = $isEmpty"); +} diff --git a/ja/codes/dart/chapter_stack_and_queue/linkedlist_deque.dart b/ja/codes/dart/chapter_stack_and_queue/linkedlist_deque.dart new file mode 100644 index 000000000..bd2c911bb --- /dev/null +++ b/ja/codes/dart/chapter_stack_and_queue/linkedlist_deque.dart @@ -0,0 +1,167 @@ +/** + * File: linkedlist_deque.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 双方向連結リストノード */ +class ListNode { + int val; // ノード値 + ListNode? next; // 後続ノードへの参照 + ListNode? prev; // 前駆ノードへの参照 + + ListNode(this.val, {this.next, this.prev}); +} + +/* 双方向連結リストに基づく双方向キュー */ +class LinkedListDeque { + late ListNode? _front; // 先頭ノード _front + late ListNode? _rear; // 末尾ノード _rear + int _queSize = 0; // 両端キューの長さ + + LinkedListDeque() { + this._front = null; + this._rear = null; + } + + /* 両端キューの長さを取得 */ + int size() { + return this._queSize; + } + + /* 両端キューが空かどうかを判定 */ + bool isEmpty() { + return size() == 0; + } + + /* エンキュー操作 */ + void push(int _num, bool isFront) { + final ListNode node = ListNode(_num); + if (isEmpty()) { + // 連結リストが空なら、`_front` と `_rear` の両方を `node` に向ける + _front = _rear = node; + } else if (isFront) { + // 先頭へのエンキュー操作 + // node を連結リストの先頭に追加する + _front!.prev = node; + node.next = _front; + _front = node; // 先頭ノードを更新する + } else { + // 末尾へのエンキュー操作 + // node を連結リストの末尾に追加する + _rear!.next = node; + node.prev = _rear; + _rear = node; // 末尾ノードを更新する + } + _queSize++; // キューの長さを更新 + } + + /* キュー先頭にエンキュー */ + void pushFirst(int _num) { + push(_num, true); + } + + /* キュー末尾にエンキュー */ + void pushLast(int _num) { + push(_num, false); + } + + /* デキュー操作 */ + int? pop(bool isFront) { + // キューが空なら、そのまま `null` を返す + if (isEmpty()) { + return null; + } + final int val; + if (isFront) { + // キュー先頭からの取り出し + val = _front!.val; // 先頭ノードの値を一時保存 + // 先頭ノードを削除 + ListNode? fNext = _front!.next; + if (fNext != null) { + fNext.prev = null; + _front!.next = null; + } + _front = fNext; // 先頭ノードを更新する + } else { + // キュー末尾からの取り出し + val = _rear!.val; // 末尾ノードの値を一時保存 + // 末尾ノードを削除 + ListNode? rPrev = _rear!.prev; + if (rPrev != null) { + rPrev.next = null; + _rear!.prev = null; + } + _rear = rPrev; // 末尾ノードを更新する + } + _queSize--; // キューの長さを更新 + return val; + } + + /* キュー先頭からデキュー */ + int? popFirst() { + return pop(true); + } + + /* キュー末尾からデキュー */ + int? popLast() { + return pop(false); + } + + /* キュー先頭の要素にアクセス */ + int? peekFirst() { + return _front?.val; + } + + /* キュー末尾の要素にアクセス */ + int? peekLast() { + return _rear?.val; + } + + /* 出力用の配列を返す */ + List toArray() { + ListNode? node = _front; + final List res = []; + for (int i = 0; i < _queSize; i++) { + res.add(node!.val); + node = node.next; + } + return res; + } +} + +/* Driver Code */ +void main() { + /* 両端キューを初期化 */ + final LinkedListDeque deque = LinkedListDeque(); + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + print("両端キュー deque = ${deque.toArray()}"); + + /* 要素にアクセス */ + int? peekFirst = deque.peekFirst(); + print("先頭要素 peekFirst = $peekFirst"); + int? peekLast = deque.peekLast(); + print("末尾要素 peekLast = $peekLast"); + + /* 要素をエンキュー */ + deque.pushLast(4); + print("要素 4 を末尾に追加した後 deque = ${deque.toArray()}"); + deque.pushFirst(1); + print("要素 1 を先頭に追加した後 deque = ${deque.toArray()}"); + + /* 要素をデキュー */ + int? popLast = deque.popLast(); + print("末尾から取り出した要素 = $popLast ,末尾から取り出した後 deque = ${deque.toArray()}"); + int? popFirst = deque.popFirst(); + print("先頭から取り出した要素 = $popFirst ,先頭から取り出した後 deque = ${deque.toArray()}"); + + /* 両端キューの長さを取得 */ + int size = deque.size(); + print("両端キューの長さ size = $size"); + + /* 両端キューが空かどうかを判定 */ + bool isEmpty = deque.isEmpty(); + print("両端キューが空かどうか = $isEmpty"); +} diff --git a/ja/codes/dart/chapter_stack_and_queue/linkedlist_queue.dart b/ja/codes/dart/chapter_stack_and_queue/linkedlist_queue.dart new file mode 100644 index 000000000..9d6d2f30b --- /dev/null +++ b/ja/codes/dart/chapter_stack_and_queue/linkedlist_queue.dart @@ -0,0 +1,103 @@ +/** + * File: linkedlist_queue.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/list_node.dart'; + +/* 連結リストベースのキュー */ +class LinkedListQueue { + ListNode? _front; // 先頭ノード _front + ListNode? _rear; // 末尾ノード _rear + int _queSize = 0; // キューの長さ + + LinkedListQueue() { + _front = null; + _rear = null; + } + + /* キューの長さを取得 */ + int size() { + return _queSize; + } + + /* キューが空かどうかを判定 */ + bool isEmpty() { + return _queSize == 0; + } + + /* エンキュー */ + void push(int _num) { + // 末尾ノードの後ろに _num を追加 + final node = ListNode(_num); + // キューが空なら、先頭・末尾ノードをともにそのノードに設定 + if (_front == null) { + _front = node; + _rear = node; + } else { + // キューが空でなければ、そのノードを末尾ノードの後ろに追加 + _rear!.next = node; + _rear = node; + } + _queSize++; + } + + /* デキュー */ + int pop() { + final int _num = peek(); + // 先頭ノードを削除 + _front = _front!.next; + _queSize--; + return _num; + } + + /* キュー先頭の要素にアクセス */ + int peek() { + if (_queSize == 0) { + throw Exception('キューが空です'); + } + return _front!.val; + } + + /* 連結リストを Array に変換して返す */ + List toArray() { + ListNode? node = _front; + final List queue = []; + while (node != null) { + queue.add(node.val); + node = node.next; + } + return queue; + } +} + +/* Driver Code */ +void main() { + /* キューを初期化 */ + final queue = LinkedListQueue(); + + /* 要素をエンキュー */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + print("キュー queue = ${queue.toArray()}"); + + /* キュー先頭の要素にアクセス */ + final int peek = queue.peek(); + print("先頭要素 peek = $peek"); + + /* 要素をデキュー */ + final int pop = queue.pop(); + print("デキューした要素 pop = $pop ,デキュー後の queue = ${queue.toArray()}"); + + /* キューの長さを取得 */ + final int size = queue.size(); + print("キューの長さ size = $size"); + + /* キューが空かどうかを判定 */ + final bool isEmpty = queue.isEmpty(); + print("キューが空かどうか = $isEmpty"); +} diff --git a/ja/codes/dart/chapter_stack_and_queue/linkedlist_stack.dart b/ja/codes/dart/chapter_stack_and_queue/linkedlist_stack.dart new file mode 100644 index 000000000..6ad2e84e3 --- /dev/null +++ b/ja/codes/dart/chapter_stack_and_queue/linkedlist_stack.dart @@ -0,0 +1,93 @@ +/** + * File: linkedlist_stack.dart + * Created Time: 2023-03-27 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/list_node.dart'; + +/* 連結リストクラスに基づくスタック */ +class LinkedListStack { + ListNode? _stackPeek; // 先頭ノードをスタックトップとする + int _stkSize = 0; // スタックの長さ + + LinkedListStack() { + _stackPeek = null; + } + + /* スタックの長さを取得 */ + int size() { + return _stkSize; + } + + /* スタックが空かどうかを判定 */ + bool isEmpty() { + return _stkSize == 0; + } + + /* プッシュ */ + void push(int _num) { + final ListNode node = ListNode(_num); + node.next = _stackPeek; + _stackPeek = node; + _stkSize++; + } + + /* ポップ */ + int pop() { + final int _num = peek(); + _stackPeek = _stackPeek!.next; + _stkSize--; + return _num; + } + + /* スタックトップの要素にアクセス */ + int peek() { + if (_stackPeek == null) { + throw Exception("スタックが空です"); + } + return _stackPeek!.val; + } + + /* 連結リストを List に変換して返す */ + List toList() { + ListNode? node = _stackPeek; + List list = []; + while (node != null) { + list.add(node.val); + node = node.next; + } + list = list.reversed.toList(); + return list; + } +} + +/* Driver Code */ +void main() { + /* スタックを初期化 */ + final LinkedListStack stack = LinkedListStack(); + + /* 要素をプッシュ */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print("スタック stack = ${stack.toList()}"); + + /* スタックトップの要素にアクセス */ + final int peek = stack.peek(); + print("スタックトップの要素 peek = $peek"); + + /* 要素をポップ */ + final int pop = stack.pop(); + print("ポップした要素 pop = $pop ,ポップ後の stack = ${stack.toList()}"); + + /* スタックの長さを取得 */ + final int size = stack.size(); + print("スタックの長さ size = $size"); + + /* 空かどうかを判定 */ + final bool isEmpty = stack.isEmpty(); + print("スタックが空かどうか = $isEmpty"); +} diff --git a/ja/codes/dart/chapter_stack_and_queue/queue.dart b/ja/codes/dart/chapter_stack_and_queue/queue.dart new file mode 100644 index 000000000..d3a1b6d4c --- /dev/null +++ b/ja/codes/dart/chapter_stack_and_queue/queue.dart @@ -0,0 +1,37 @@ +/** + * File: queue.dart + * Created Time: 2023-03-28 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:collection'; + +void main() { + /* キューを初期化 */ + // Dart では、通常は両端キュー Queue をキューとして使う + final Queue queue = Queue(); + + /* 要素をエンキュー */ + queue.add(1); + queue.add(3); + queue.add(2); + queue.add(5); + queue.add(4); + print("キュー queue = $queue"); + + /* キュー先頭の要素にアクセス */ + final int peek = queue.first; + print("先頭要素 peek = $peek"); + + /* 要素をデキュー */ + final int pop = queue.removeFirst(); + print("デキューした要素 pop = $pop ,デキュー後の queue = $queue"); + + /* キューの長さを取得 */ + final int size = queue.length; + print("キューの長さ size = $size"); + + /* キューが空かどうかを判定 */ + final bool isEmpty = queue.isEmpty; + print("キューが空かどうか = $isEmpty"); +} diff --git a/ja/codes/dart/chapter_stack_and_queue/stack.dart b/ja/codes/dart/chapter_stack_and_queue/stack.dart new file mode 100644 index 000000000..0da25e9b3 --- /dev/null +++ b/ja/codes/dart/chapter_stack_and_queue/stack.dart @@ -0,0 +1,35 @@ +/** + * File: stack.dart + * Created Time: 2023-03-27 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +void main() { + /* スタックを初期化 */ + // Dart には組み込みのスタッククラスがないため、List をスタックとして使える + final List stack = []; + + /* 要素をプッシュ */ + stack.add(1); + stack.add(3); + stack.add(2); + stack.add(5); + stack.add(4); + print("スタック stack = $stack"); + + /* スタックトップの要素にアクセス */ + final int peek = stack.last; + print("スタックトップの要素 peek = $peek"); + + /* 要素をポップ */ + final int pop = stack.removeLast(); + print("ポップした要素 pop = $pop ,ポップ後の stack = $stack"); + + /* スタックの長さを取得 */ + final int size = stack.length; + print("スタックの長さ size = $size"); + + /* 空かどうかを判定 */ + final bool isEmpty = stack.isEmpty; + print("スタックが空かどうか = $isEmpty"); +} diff --git a/ja/codes/dart/chapter_tree/array_binary_tree.dart b/ja/codes/dart/chapter_tree/array_binary_tree.dart new file mode 100644 index 000000000..c201a49a5 --- /dev/null +++ b/ja/codes/dart/chapter_tree/array_binary_tree.dart @@ -0,0 +1,152 @@ +/** + * File: array_binary_tree.dart + * Created Time: 2023-08-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 配列表現による二分木クラス */ +class ArrayBinaryTree { + late List _tree; + + /* コンストラクタ */ + ArrayBinaryTree(this._tree); + + /* リスト容量 */ + int size() { + return _tree.length; + } + + /* インデックス i のノードの値を取得 */ + int? val(int i) { + // インデックスが範囲外なら、空きを表す null を返す + if (i < 0 || i >= size()) { + return null; + } + return _tree[i]; + } + + /* インデックス i のノードの左子ノードのインデックスを取得 */ + int? left(int i) { + return 2 * i + 1; + } + + /* インデックス i のノードの右子ノードのインデックスを取得 */ + int? right(int i) { + return 2 * i + 2; + } + + /* インデックス i のノードの親ノードのインデックスを取得 */ + int? parent(int i) { + return (i - 1) ~/ 2; + } + + /* レベル順走査 */ + List levelOrder() { + List res = []; + for (int i = 0; i < size(); i++) { + if (val(i) != null) { + res.add(val(i)!); + } + } + return res; + } + + /* 深さ優先探索 */ + void dfs(int i, String order, List res) { + // 空きスロットなら返す + if (val(i) == null) { + return; + } + // 先行順走査 + if (order == 'pre') { + res.add(val(i)); + } + dfs(left(i)!, order, res); + // 中順走査 + if (order == 'in') { + res.add(val(i)); + } + dfs(right(i)!, order, res); + // 後順走査 + if (order == 'post') { + res.add(val(i)); + } + } + + /* 先行順走査 */ + List preOrder() { + List res = []; + dfs(0, 'pre', res); + return res; + } + + /* 中順走査 */ + List inOrder() { + List res = []; + dfs(0, 'in', res); + return res; + } + + /* 後順走査 */ + List postOrder() { + List res = []; + dfs(0, 'post', res); + return res; + } +} + +/* Driver Code */ +void main() { + // 二分木を初期化 + // ここでは、配列から直接二分木を生成する関数を利用する + List arr = [ + 1, + 2, + 3, + 4, + null, + 6, + 7, + 8, + 9, + null, + null, + 12, + null, + null, + 15 + ]; + + TreeNode? root = listToTree(arr); + print("\n二分木を初期化\n"); + print("二分木の配列表現:"); + print(arr); + print("二分木の連結リスト表現:"); + printTree(root); + + // 配列表現による二分木クラス + ArrayBinaryTree abt = ArrayBinaryTree(arr); + + // ノードにアクセス + int i = 1; + int? l = abt.left(i); + int? r = abt.right(i); + int? p = abt.parent(i); + print("\n現在のノードのインデックスは $i ,値は ${abt.val(i)}"); + print("その左子ノードのインデックスは $l ,値は ${(l == null ? "null" : abt.val(l))}"); + print("その右子ノードのインデックスは $r ,値は ${(r == null ? "null" : abt.val(r))}"); + print("その親ノードのインデックスは $p ,値は ${(p == null ? "null" : abt.val(p))}"); + + // 木を走査 + List res = abt.levelOrder(); + print("\nレベル順走査:$res"); + res = abt.preOrder(); + print("先行順走査は $res"); + res = abt.inOrder(); + print("中間順走査は $res"); + res = abt.postOrder(); + print("後行順走査は $res"); +} diff --git a/ja/codes/dart/chapter_tree/avl_tree.dart b/ja/codes/dart/chapter_tree/avl_tree.dart new file mode 100644 index 000000000..ceabdf59a --- /dev/null +++ b/ja/codes/dart/chapter_tree/avl_tree.dart @@ -0,0 +1,218 @@ +/** + * File: avl_tree.dart + * Created Time: 2023-04-04 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import 'dart:math'; +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +class AVLTree { + TreeNode? root; + + /* コンストラクタ */ + AVLTree() { + root = null; + } + + /* ノードの高さを取得 */ + int height(TreeNode? node) { + // 空ノードの高さは -1、葉ノードの高さは 0 + return node == null ? -1 : node.height; + } + + /* ノードの高さを更新する */ + void updateHeight(TreeNode? node) { + // ノードの高さは最も高い部分木の高さ + 1 に等しい + node!.height = max(height(node.left), height(node.right)) + 1; + } + + /* 平衡係数を取得 */ + int balanceFactor(TreeNode? node) { + // 空ノードの平衡係数は 0 + if (node == null) return 0; + // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ + return height(node.left) - height(node.right); + } + + /* 右回転 */ + TreeNode? rightRotate(TreeNode? node) { + TreeNode? child = node!.left; + TreeNode? grandChild = child!.right; + // child を支点として node を右回転させる + child.right = node; + node.left = grandChild; + // ノードの高さを更新する + updateHeight(node); + updateHeight(child); + // 回転後の部分木の根ノードを返す + return child; + } + + /* 左回転 */ + TreeNode? leftRotate(TreeNode? node) { + TreeNode? child = node!.right; + TreeNode? grandChild = child!.left; + // child を支点として node を左回転させる + child.left = node; + node.right = grandChild; + // ノードの高さを更新する + updateHeight(node); + updateHeight(child); + // 回転後の部分木の根ノードを返す + return child; + } + + /* 回転操作を行い、この部分木の平衡を回復する */ + TreeNode? rotate(TreeNode? node) { + // ノード node の平衡係数を取得 + int factor = balanceFactor(node); + // 左に偏った木 + if (factor > 1) { + if (balanceFactor(node!.left) >= 0) { + // 右回転 + return rightRotate(node); + } else { + // 左回転してから右回転 + node.left = leftRotate(node.left); + return rightRotate(node); + } + } + // 右に偏った木 + if (factor < -1) { + if (balanceFactor(node!.right) <= 0) { + // 左回転 + return leftRotate(node); + } else { + // 右回転してから左回転 + node.right = rightRotate(node.right); + return leftRotate(node); + } + } + // 平衡木なので回転不要、そのまま返す + return node; + } + + /* ノードを挿入 */ + void insert(int val) { + root = insertHelper(root, val); + } + + /* ノードを再帰的に挿入する(補助メソッド) */ + TreeNode? insertHelper(TreeNode? node, int val) { + if (node == null) return TreeNode(val); + /* 1. 挿入位置を探索してノードを挿入 */ + if (val < node.val) + node.left = insertHelper(node.left, val); + else if (val > node.val) + node.right = insertHelper(node.right, val); + else + return node; // 重複ノードは挿入せず、そのまま返す + updateHeight(node); // ノードの高さを更新する + /* 2. 回転操作を行い、部分木の平衡を回復する */ + node = rotate(node); + // 部分木の根ノードを返す + return node; + } + + /* ノードを削除 */ + void remove(int val) { + root = removeHelper(root, val); + } + + /* ノードを再帰的に削除する(補助メソッド) */ + TreeNode? removeHelper(TreeNode? node, int val) { + if (node == null) return null; + /* 1. ノードを探索して削除 */ + if (val < node.val) + node.left = removeHelper(node.left, val); + else if (val > node.val) + node.right = removeHelper(node.right, val); + else { + if (node.left == null || node.right == null) { + TreeNode? child = node.left ?? node.right; + // 子ノード数 = 0 の場合、node をそのまま削除して返す + if (child == null) + return null; + // 子ノード数 = 1 の場合、node をそのまま削除する + else + node = child; + } else { + // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える + TreeNode? temp = node.right; + while (temp!.left != null) { + temp = temp.left; + } + node.right = removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + updateHeight(node); // ノードの高さを更新する + /* 2. 回転操作を行い、部分木の平衡を回復する */ + node = rotate(node); + // 部分木の根ノードを返す + return node; + } + + /* ノードを探索 */ + TreeNode? search(int val) { + TreeNode? cur = root; + // ループで探索し、葉ノードを越えたら抜ける + while (cur != null) { + // 目標ノードは cur の右部分木にある + if (val < cur.val) + cur = cur.left; + // 目標ノードは cur の左部分木にある + else if (val > cur.val) + cur = cur.right; + // 対象ノードが現在のノードと等しい + else + break; + } + return cur; + } +} + +void testInsert(AVLTree tree, int val) { + tree.insert(val); + print("\nノード $val を挿入後、AVL 木は"); + printTree(tree.root); +} + +void testRemove(AVLTree tree, int val) { + tree.remove(val); + print("\nノード $val を削除後、AVL 木は"); + printTree(tree.root); +} + +/* Driver Code */ +void main() { + /* 空の AVL 木を初期化する */ + AVLTree avlTree = AVLTree(); + /* ノードを挿入 */ + // ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい + testInsert(avlTree, 1); + testInsert(avlTree, 2); + testInsert(avlTree, 3); + testInsert(avlTree, 4); + testInsert(avlTree, 5); + testInsert(avlTree, 8); + testInsert(avlTree, 7); + testInsert(avlTree, 9); + testInsert(avlTree, 10); + testInsert(avlTree, 6); + + /* 重複ノードを挿入する */ + testInsert(avlTree, 7); + + /* ノードを削除 */ + // ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい + testRemove(avlTree, 8); // 次数 0 のノードを削除する + testRemove(avlTree, 5); // 次数 1 のノードを削除する + testRemove(avlTree, 4); // 次数 2 のノードを削除する + + /* ノードを検索 */ + TreeNode? node = avlTree.search(7); + print("\n見つかったノードオブジェクトは $node ,ノードの値 = ${node!.val}"); +} diff --git a/ja/codes/dart/chapter_tree/binary_search_tree.dart b/ja/codes/dart/chapter_tree/binary_search_tree.dart new file mode 100644 index 000000000..5b1741488 --- /dev/null +++ b/ja/codes/dart/chapter_tree/binary_search_tree.dart @@ -0,0 +1,153 @@ +/** + * File: binary_search_tree.dart + * Created Time: 2023-04-04 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* 二分探索木 */ +class BinarySearchTree { + late TreeNode? _root; + + /* コンストラクタ */ + BinarySearchTree() { + // 空の木を初期化する + _root = null; + } + + /* 二分木の根ノードを取得する */ + TreeNode? getRoot() { + return _root; + } + + /* ノードを探索 */ + TreeNode? search(int _num) { + TreeNode? cur = _root; + // ループで探索し、葉ノードを越えたら抜ける + while (cur != null) { + // 目標ノードは cur の右部分木にある + if (cur.val < _num) + cur = cur.right; + // 目標ノードは cur の左部分木にある + else if (cur.val > _num) + cur = cur.left; + // 目標ノードが見つかったらループを抜ける + else + break; + } + // 目標ノードを返す + return cur; + } + + /* ノードを挿入 */ + void insert(int _num) { + // 木が空なら、根ノードを初期化する + if (_root == null) { + _root = TreeNode(_num); + return; + } + TreeNode? cur = _root; + TreeNode? pre = null; + // ループで探索し、葉ノードを越えたら抜ける + while (cur != null) { + // 重複ノードが見つかったら、直ちに返す + if (cur.val == _num) return; + pre = cur; + // 挿入位置は cur の右部分木にある + if (cur.val < _num) + cur = cur.right; + // 挿入位置は cur の左部分木にある + else + cur = cur.left; + } + // ノードを挿入 + TreeNode? node = TreeNode(_num); + if (pre!.val < _num) + pre.right = node; + else + pre.left = node; + } + + /* ノードを削除 */ + void remove(int _num) { + // 木が空なら、そのまま早期リターンする + if (_root == null) return; + TreeNode? cur = _root; + TreeNode? pre = null; + // ループで探索し、葉ノードを越えたら抜ける + while (cur != null) { + // 削除対象のノードが見つかったら、ループを抜ける + if (cur.val == _num) break; + pre = cur; + // 削除対象ノードは cur の右部分木にある + if (cur.val < _num) + cur = cur.right; + // 削除対象ノードは cur の左部分木にある + else + cur = cur.left; + } + // 削除対象ノードがない場合は、そのまま返す + if (cur == null) return; + // 子ノード数 = 0 or 1 + if (cur.left == null || cur.right == null) { + // 子ノード数が 0 / 1 のとき、child = null / その子ノード + TreeNode? child = cur.left ?? cur.right; + // ノード cur を削除する + if (cur != _root) { + if (pre!.left == cur) + pre.left = child; + else + pre.right = child; + } else { + // 削除ノードが根ノードなら、根ノードを再設定 + _root = child; + } + } else { + // 子ノード数 = 2 + // 中順走査における cur の次のノードを取得 + TreeNode? tmp = cur.right; + while (tmp!.left != null) { + tmp = tmp.left; + } + // ノード tmp を再帰的に削除 + remove(tmp.val); + // tmp で cur を上書きする + cur.val = tmp.val; + } + } +} + +/* Driver Code */ +void main() { + /* 二分探索木を初期化 */ + BinarySearchTree bst = BinarySearchTree(); + // 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる + List nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; + for (int _num in nums) { + bst.insert(_num); + } + print("\n初期化した二分木は\n"); + printTree(bst.getRoot()); + + /* ノードを探索 */ + TreeNode? node = bst.search(7); + print("\n見つかったノードオブジェクトは $node ,ノードの値 = ${node?.val}"); + + /* ノードを挿入 */ + bst.insert(16); + print("\nノード 16 を挿入後、二分木は\n"); + printTree(bst.getRoot()); + + /* ノードを削除 */ + bst.remove(1); + print("\nノード 1 を削除後、二分木は\n"); + printTree(bst.getRoot()); + bst.remove(2); + print("\nノード 2 を削除後、二分木は\n"); + printTree(bst.getRoot()); + bst.remove(4); + print("\nノード 4 を削除後、二分木は\n"); + printTree(bst.getRoot()); +} diff --git a/ja/codes/dart/chapter_tree/binary_tree.dart b/ja/codes/dart/chapter_tree/binary_tree.dart new file mode 100644 index 000000000..35cb04ff2 --- /dev/null +++ b/ja/codes/dart/chapter_tree/binary_tree.dart @@ -0,0 +1,37 @@ +/** + * File: binary_tree.dart + * Created Time: 2023-04-03 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +void main() { + /* 二分木を初期化 */ + // ノードをヒープ化 + TreeNode n1 = TreeNode(1); + TreeNode n2 = TreeNode(2); + TreeNode n3 = TreeNode(3); + TreeNode n4 = TreeNode(4); + TreeNode n5 = TreeNode(5); + // ノード間の参照(ポインタ)を構築する + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + print("\n二分木を初期化\n"); + printTree(n1); + + /* ノードの挿入と削除 */ + TreeNode p = TreeNode(0); + // n1 -> n2 の間にノード p を挿入する + n1.left = p; + p.left = n2; + print("\nノード P を挿入後\n"); + printTree(n1); + // ノード P を削除 + n1.left = n2; + print("\nノード P を削除後\n"); + printTree(n1); +} diff --git a/ja/codes/dart/chapter_tree/binary_tree_bfs.dart b/ja/codes/dart/chapter_tree/binary_tree_bfs.dart new file mode 100644 index 000000000..5ef158447 --- /dev/null +++ b/ja/codes/dart/chapter_tree/binary_tree_bfs.dart @@ -0,0 +1,38 @@ +/** + * File: binary_tree_bfs.dart + * Created Time: 2023-04-03 + * Author: liuyuxin (gvenusleo@gmai.com) + */ + +import 'dart:collection'; +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +/* レベル順走査 */ +List levelOrder(TreeNode? root) { + // キューを初期化し、ルートノードを追加する + Queue queue = Queue(); + queue.add(root); + // 走査順序を保存するためのリストを初期化する + List res = []; + while (queue.isNotEmpty) { + TreeNode? node = queue.removeFirst(); // デキュー + res.add(node!.val); // ノードの値を保存する + if (node.left != null) queue.add(node.left); // 左子ノードをキューに追加 + if (node.right != null) queue.add(node.right); // 右子ノードをキューに追加 + } + return res; +} + +/* Driver Code */ +void main() { + /* 二分木を初期化 */ + // ここでは、配列から直接二分木を生成する関数を利用する + TreeNode? root = listToTree([1, 2, 3, 4, 5, 6, 7]); + print("\n二分木を初期化\n"); + printTree(root); + + // レベル順走査 + List res = levelOrder(root); + print("\nレベル順走査のノード出力順 = $res"); +} diff --git a/ja/codes/dart/chapter_tree/binary_tree_dfs.dart b/ja/codes/dart/chapter_tree/binary_tree_dfs.dart new file mode 100644 index 000000000..a61d05bbb --- /dev/null +++ b/ja/codes/dart/chapter_tree/binary_tree_dfs.dart @@ -0,0 +1,62 @@ +/** + * File: binary_tree_dfs.dart + * Created Time: 2023-04-04 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +import '../utils/print_util.dart'; +import '../utils/tree_node.dart'; + +// 走査順序を格納するリストを初期化 +List list = []; + +/* 先行順走査 */ +void preOrder(TreeNode? node) { + if (node == null) return; + // 訪問順序:根ノード -> 左部分木 -> 右部分木 + list.add(node.val); + preOrder(node.left); + preOrder(node.right); +} + +/* 中順走査 */ +void inOrder(TreeNode? node) { + if (node == null) return; + // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 + inOrder(node.left); + list.add(node.val); + inOrder(node.right); +} + +/* 後順走査 */ +void postOrder(TreeNode? node) { + if (node == null) return; + // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード + postOrder(node.left); + postOrder(node.right); + list.add(node.val); +} + +/* Driver Code */ +void main() { + /* 二分木を初期化 */ + // ここでは、配列から直接二分木を生成する関数を利用する + TreeNode? root = listToTree([1, 2, 3, 4, 5, 6, 7]); + print("\n二分木を初期化\n"); + printTree(root); + + /* 先行順走査 */ + list.clear(); + preOrder(root); + print("\n先行順走査のノード出力順 = $list"); + + /* 中順走査 */ + list.clear(); + inOrder(root); + print("\n中間順走査のノード出力順 = $list"); + + /* 後順走査 */ + list.clear(); + postOrder(root); + print("\n後行順走査のノード出力順 = $list"); +} diff --git a/ja/codes/dart/utils/list_node.dart b/ja/codes/dart/utils/list_node.dart new file mode 100644 index 000000000..e6466905d --- /dev/null +++ b/ja/codes/dart/utils/list_node.dart @@ -0,0 +1,24 @@ +/** + * File: list_node.dart + * Created Time: 2023-01-23 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 連結リストノード */ +class ListNode { + int val; + ListNode? next; + + ListNode(this.val, [this.next]); +} + +/* リストを連結リストにデシリアライズする */ +ListNode? listToLinkedList(List list) { + ListNode dum = ListNode(0); + ListNode? head = dum; + for (int val in list) { + head?.next = ListNode(val); + head = head?.next; + } + return dum.next; +} diff --git a/ja/codes/dart/utils/print_util.dart b/ja/codes/dart/utils/print_util.dart new file mode 100644 index 000000000..6e9ff8d60 --- /dev/null +++ b/ja/codes/dart/utils/print_util.dart @@ -0,0 +1,90 @@ +/** + * File: print_util.dart + * Created Time: 2023-01-23 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +import 'dart:io'; + +import 'list_node.dart'; +import 'tree_node.dart'; + +class Trunk { + Trunk? prev; + String str; + + Trunk(this.prev, this.str); +} + +/* 行列を出力する (Array) */ +void printMatrix(List> matrix) { + print("["); + for (List row in matrix) { + print(" $row,"); + } + print("]"); +} + +/* 連結リストを出力 */ +void printLinkedList(ListNode? head) { + List list = []; + + while (head != null) { + list.add('${head.val}'); + head = head.next; + } + + print(list.join(' -> ')); +} + +/** + * 二分木を出力 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +void printTree(TreeNode? root, [Trunk? prev = null, bool isRight = false]) { + if (root == null) { + return; + } + + String prev_str = ' '; + Trunk trunk = Trunk(prev, prev_str); + + printTree(root.right, trunk, true); + + if (prev == null) { + trunk.str = '———'; + } else if (isRight) { + trunk.str = '/———'; + prev_str = ' |'; + } else { + trunk.str = '\\———'; + prev.str = prev_str; + } + showTrunks(trunk); + print(' ${root.val}'); + + if (prev != null) { + prev.str = prev_str; + } + trunk.str = ' |'; + + printTree(root.left, trunk, false); +} + +void showTrunks(Trunk? p) { + if (p == null) { + return; + } + + showTrunks(p.prev); + stdout.write(p.str); +} + +/* ヒープを出力 */ +void printHeap(List heap) { + print("ヒープの配列表現:$heap"); + print("ヒープの木構造表示:"); + TreeNode? root = listToTree(heap); + printTree(root); +} diff --git a/ja/codes/dart/utils/tree_node.dart b/ja/codes/dart/utils/tree_node.dart new file mode 100644 index 000000000..35310abd1 --- /dev/null +++ b/ja/codes/dart/utils/tree_node.dart @@ -0,0 +1,50 @@ +/** + * File: tree_node.dart + * Created Time: 2023-2-12 + * Author: Jefferson (JeffersonHuang77@gmail.com) + */ + +/* 二分木ノードクラス */ +class TreeNode { + int val; // ノード値 + int height; // ノードの高さ + TreeNode? left; // 左子ノードへの参照 + TreeNode? right; // 右子ノードへの参照 + + /* コンストラクタ */ + TreeNode(this.val, [this.height = 0, this.left, this.right]); +} + +/* リストを二分木にデシリアライズする: 再帰 */ +TreeNode? listToTreeDFS(List arr, int i) { + if (i < 0 || i >= arr.length || arr[i] == null) { + return null; + } + TreeNode? root = TreeNode(arr[i]!); + root.left = listToTreeDFS(arr, 2 * i + 1); + root.right = listToTreeDFS(arr, 2 * i + 2); + return root; +} + +/* リストを二分木にデシリアライズする */ +TreeNode? listToTree(List arr) { + return listToTreeDFS(arr, 0); +} + +/* 二分木をリストにシリアライズする: 再帰 */ +void treeToListDFS(TreeNode? root, int i, List res) { + if (root == null) return; + while (i >= res.length) { + res.add(null); + } + res[i] = root.val; + treeToListDFS(root.left, 2 * i + 1, res); + treeToListDFS(root.right, 2 * i + 2, res); +} + +/* 二分木をリストにシリアライズする */ +List treeToList(TreeNode? root) { + List res = []; + treeToListDFS(root, 0, res); + return res; +} diff --git a/ja/codes/dart/utils/vertex.dart b/ja/codes/dart/utils/vertex.dart new file mode 100644 index 000000000..3841cf1b6 --- /dev/null +++ b/ja/codes/dart/utils/vertex.dart @@ -0,0 +1,29 @@ +/** + * File: Vertex.dart + * Created Time: 2023-05-15 + * Author: liuyuxin (gvenusleo@gmail.com) + */ + +/* 頂点クラス */ +class Vertex { + int val; + Vertex(this.val); + + /* 値リスト vals を入力し、頂点リスト vets を返す */ + static List valsToVets(List vals) { + List vets = []; + for (int i in vals) { + vets.add(Vertex(i)); + } + return vets; + } + + /* 頂点リスト vets を入力し、値リスト vals を返す */ + static List vetsToVals(List vets) { + List vals = []; + for (Vertex vet in vets) { + vals.add(vet.val); + } + return vals; + } +} diff --git a/ja/codes/go/chapter_array_and_linkedlist/array.go b/ja/codes/go/chapter_array_and_linkedlist/array.go new file mode 100644 index 000000000..3378a6963 --- /dev/null +++ b/ja/codes/go/chapter_array_and_linkedlist/array.go @@ -0,0 +1,79 @@ +// File: array.go +// Created Time: 2022-12-29 +// Author: GuoWei (gongguowei01@gmail.com), cathay (cathaycchen@gmail.com) + +package chapter_array_and_linkedlist + +import ( + "math/rand" +) + +/* 要素へランダムアクセス */ +func randomAccess(nums []int) (randomNum int) { + // 区間 [0, nums.length) からランダムに 1 つの数を選ぶ + randomIndex := rand.Intn(len(nums)) + // ランダムな要素を取得して返す + randomNum = nums[randomIndex] + return +} + +/* 配列長を拡張する */ +func extend(nums []int, enlarge int) []int { + // 拡張後の長さを持つ配列を初期化する + res := make([]int, len(nums)+enlarge) + // 元の配列の全要素を新しい配列にコピー + for i, num := range nums { + res[i] = num + } + // 拡張後の新しい配列を返す + return res +} + +/* 配列の index 番目に要素 num を挿入 */ +func insert(nums []int, num int, index int) { + // インデックス index 以降の全要素を 1 つ後ろへ移動する + for i := len(nums) - 1; i > index; i-- { + nums[i] = nums[i-1] + } + // index の要素に num を代入する + nums[index] = num +} + +/* index の要素を削除する */ +func remove(nums []int, index int) { + // インデックス index より後ろの全要素を 1 つ前へ移動する + for i := index; i < len(nums)-1; i++ { + nums[i] = nums[i+1] + } +} + +/* 配列を走査 */ +func traverse(nums []int) { + count := 0 + // インデックスで配列を走査 + for i := 0; i < len(nums); i++ { + count += nums[i] + } + count = 0 + // 配列要素を直接走査 + for _, num := range nums { + count += num + } + // データのインデックスと要素を同時に走査する + for i, num := range nums { + count += nums[i] + count += num + } +} + +/* 配列内で指定要素を探す */ +func find(nums []int, target int) (index int) { + index = -1 + for i := 0; i < len(nums); i++ { + if nums[i] == target { + index = i + break + } + } + return +} diff --git a/ja/codes/go/chapter_array_and_linkedlist/array_test.go b/ja/codes/go/chapter_array_and_linkedlist/array_test.go new file mode 100644 index 000000000..590b9e8fb --- /dev/null +++ b/ja/codes/go/chapter_array_and_linkedlist/array_test.go @@ -0,0 +1,50 @@ +// File: array_test.go +// Created Time: 2022-12-29 +// Author: GuoWei (gongguowei01@gmail.com), cathay (cathaycchen@gmail.com) + +package chapter_array_and_linkedlist + +/** + * ここでは Go の Slice を Array 配列とみなします。これは + * 理解コストを下げ、データ構造とアルゴリズムに集中しやすくするためです。 + */ + +import ( + "fmt" + "testing" +) + +/* Driver Code */ +func TestArray(t *testing.T) { + /* 配列を初期化 */ + var arr [5]int + fmt.Println("配列 arr =", arr) + // Go では、長さを指定する場合([5]int)は配列、指定しない場合([]int)はスライスである + // Go の配列はコンパイル時に長さが確定するよう設計されているため、長さには定数しか使えない + // extend() 関数を実装しやすくするため、以下ではスライス(Slice)を配列(Array)として扱う + nums := []int{1, 3, 2, 5, 4} + fmt.Println("配列 nums =", nums) + + /* ランダムアクセス */ + randomNum := randomAccess(nums) + fmt.Println("nums からランダムな要素を取得", randomNum) + + /* 長さを拡張 */ + nums = extend(nums, 3) + fmt.Println("配列の長さを 8 に拡張し,nums =", nums) + + /* 要素を挿入する */ + insert(nums, 6, 3) + fmt.Println("インデックス 3 に数値 6 を挿入し,nums =", nums) + + /* 要素を削除 */ + remove(nums, 2) + fmt.Println("インデックス 2 の要素を削除すると、nums =", nums) + + /* 配列を走査 */ + traverse(nums) + + /* 要素を探索する */ + index := find(nums, 3) + fmt.Println("nums 内で要素 3 を検索すると、インデックス =", index) +} diff --git a/ja/codes/go/chapter_array_and_linkedlist/linked_list.go b/ja/codes/go/chapter_array_and_linkedlist/linked_list.go new file mode 100644 index 000000000..c771ac665 --- /dev/null +++ b/ja/codes/go/chapter_array_and_linkedlist/linked_list.go @@ -0,0 +1,51 @@ +// File: linked_list.go +// Created Time: 2022-12-29 +// Author: cathay (cathaycchen@gmail.com) + +package chapter_array_and_linkedlist + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 連結リストでノード n0 の後ろにノード P を挿入する */ +func insertNode(n0 *ListNode, P *ListNode) { + n1 := n0.Next + P.Next = n1 + n0.Next = P +} + +/* 連結リストでノード n0 の直後のノードを削除する */ +func removeItem(n0 *ListNode) { + if n0.Next == nil { + return + } + // n0 -> P -> n1 + P := n0.Next + n1 := P.Next + n0.Next = n1 +} + +/* 連結リスト内で index 番目のノードにアクセス */ +func access(head *ListNode, index int) *ListNode { + for i := 0; i < index; i++ { + if head == nil { + return nil + } + head = head.Next + } + return head +} + +/* 連結リストで値が target の最初のノードを探す */ +func findNode(head *ListNode, target int) int { + index := 0 + for head != nil { + if head.Val == target { + return index + } + head = head.Next + index++ + } + return -1 +} diff --git a/ja/codes/go/chapter_array_and_linkedlist/linked_list_test.go b/ja/codes/go/chapter_array_and_linkedlist/linked_list_test.go new file mode 100644 index 000000000..ff0b9db45 --- /dev/null +++ b/ja/codes/go/chapter_array_and_linkedlist/linked_list_test.go @@ -0,0 +1,48 @@ +// File: linked_list_test.go +// Created Time: 2022-12-29 +// Author: cathay (cathaycchen@gmail.com) + +package chapter_array_and_linkedlist + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestLinkedList(t *testing.T) { + /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化する */ + // 各ノードを初期化 + n0 := NewListNode(1) + n1 := NewListNode(3) + n2 := NewListNode(2) + n3 := NewListNode(5) + n4 := NewListNode(4) + + // ノード間の参照を構築する + n0.Next = n1 + n1.Next = n2 + n2.Next = n3 + n3.Next = n4 + fmt.Println("初期化した連結リストは") + PrintLinkedList(n0) + + /* ノードを挿入 */ + insertNode(n0, NewListNode(0)) + fmt.Println("ノード挿入後の連結リストは") + PrintLinkedList(n0) + + /* ノードを削除 */ + removeItem(n0) + fmt.Println("ノード削除後の連結リストは") + PrintLinkedList(n0) + + /* ノードにアクセス */ + node := access(n0, 3) + fmt.Println("連結リスト内のインデックス 3 のノードの値 =", node) + + /* ノードを探索 */ + index := findNode(n0, 2) + fmt.Println("連結リスト内で値が 2 のノードのインデックス =", index) +} diff --git a/ja/codes/go/chapter_array_and_linkedlist/list_test.go b/ja/codes/go/chapter_array_and_linkedlist/list_test.go new file mode 100644 index 000000000..68a30ef3f --- /dev/null +++ b/ja/codes/go/chapter_array_and_linkedlist/list_test.go @@ -0,0 +1,66 @@ +// File: list_test.go +// Created Time: 2022-12-18 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_array_and_linkedlist + +import ( + "fmt" + "sort" + "testing" +) + +/* Driver Code */ +func TestList(t *testing.T) { + /* リストを初期化 */ + nums := []int{1, 3, 2, 5, 4} + fmt.Println("リスト nums =", nums) + + /* 要素にアクセス */ + num := nums[1] // インデックス 1 の要素にアクセス + fmt.Println("インデックス 1 の要素にアクセスすると、num =", num) + + /* 要素を更新 */ + nums[1] = 0 // 添字 1 の要素を 0 に更新 + fmt.Println("インデックス 1 の要素を 0 に更新すると、nums =", nums) + + /* リストを空にする */ + nums = nil + fmt.Println("リストを空にした後の nums =", nums) + + /* 末尾に要素を追加 */ + nums = append(nums, 1) + nums = append(nums, 3) + nums = append(nums, 2) + nums = append(nums, 5) + nums = append(nums, 4) + fmt.Println("要素追加後の nums =", nums) + + /* 中間に要素を挿入 */ + nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // 添字 3 に数字 6 を挿入 + fmt.Println("インデックス 3 に数値 6 を挿入し,nums =", nums) + + /* 要素を削除 */ + nums = append(nums[:3], nums[4:]...) // インデックス 3 の要素を削除する + fmt.Println("インデックス 3 の要素を削除すると、nums =", nums) + + /* インデックスでリストを走査 */ + count := 0 + for i := 0; i < len(nums); i++ { + count += nums[i] + } + /* リスト要素を直接走査 */ + count = 0 + for _, x := range nums { + count += x + } + + /* 2 つのリストを連結する */ + nums1 := []int{6, 8, 7, 10, 9} + nums = append(nums, nums1...) // リスト nums1 を nums の後ろに連結 + fmt.Println("リスト nums1 を nums の後ろに連結すると、nums =", nums) + + /* リストをソート */ + sort.Ints(nums) // ソート後、リスト要素は小さい順に並ぶ + fmt.Println("リストをソートすると、nums =", nums) +} diff --git a/ja/codes/go/chapter_array_and_linkedlist/my_list.go b/ja/codes/go/chapter_array_and_linkedlist/my_list.go new file mode 100644 index 000000000..194a94f89 --- /dev/null +++ b/ja/codes/go/chapter_array_and_linkedlist/my_list.go @@ -0,0 +1,109 @@ +// File: my_list.go +// Created Time: 2022-12-18 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_array_and_linkedlist + +/* リストクラス */ +type myList struct { + arrCapacity int + arr []int + arrSize int + extendRatio int +} + +/* コンストラクタ */ +func newMyList() *myList { + return &myList{ + arrCapacity: 10, // リスト容量 + arr: make([]int, 10), // 配列(リスト要素を格納) + arrSize: 0, // リストの長さ(現在の要素数) + extendRatio: 2, // リスト拡張時の増加倍率 + } +} + +/* リストの長さを取得(現在の要素数) */ +func (l *myList) size() int { + return l.arrSize +} + +/* リスト容量を取得する */ +func (l *myList) capacity() int { + return l.arrCapacity +} + +/* 要素にアクセス */ +func (l *myList) get(index int) int { + // インデックスが範囲外なら例外を送出する。以下同様 + if index < 0 || index >= l.arrSize { + panic("インデックスが範囲外です") + } + return l.arr[index] +} + +/* 要素を更新 */ +func (l *myList) set(num, index int) { + if index < 0 || index >= l.arrSize { + panic("インデックスが範囲外です") + } + l.arr[index] = num +} + +/* 末尾に要素を追加 */ +func (l *myList) add(num int) { + // 要素数が容量を超えると、拡張機構が発動する + if l.arrSize == l.arrCapacity { + l.extendCapacity() + } + l.arr[l.arrSize] = num + // 要素数を更新 + l.arrSize++ +} + +/* 中間に要素を挿入 */ +func (l *myList) insert(num, index int) { + if index < 0 || index >= l.arrSize { + panic("インデックスが範囲外です") + } + // 要素数が容量を超えると、拡張機構が発動する + if l.arrSize == l.arrCapacity { + l.extendCapacity() + } + // index 以降の要素をすべて 1 つ後ろへずらす + for j := l.arrSize - 1; j >= index; j-- { + l.arr[j+1] = l.arr[j] + } + l.arr[index] = num + // 要素数を更新 + l.arrSize++ +} + +/* 要素を削除 */ +func (l *myList) remove(index int) int { + if index < 0 || index >= l.arrSize { + panic("インデックスが範囲外です") + } + num := l.arr[index] + // インデックス index より後の要素をすべて 1 つ前に移動する + for j := index; j < l.arrSize-1; j++ { + l.arr[j] = l.arr[j+1] + } + // 要素数を更新 + l.arrSize-- + // 削除された要素を返す + return num +} + +/* リストの拡張 */ +func (l *myList) extendCapacity() { + // 元の配列の extendRatio 倍の長さを持つ新しい配列を作成し、元の配列をコピーする + l.arr = append(l.arr, make([]int, l.arrCapacity*(l.extendRatio-1))...) + // リストの容量を更新 + l.arrCapacity = len(l.arr) +} + +/* 有効長のリストを返す */ +func (l *myList) toArray() []int { + // 有効長の範囲内のリスト要素のみを変換 + return l.arr[:l.arrSize] +} diff --git a/ja/codes/go/chapter_array_and_linkedlist/my_list_test.go b/ja/codes/go/chapter_array_and_linkedlist/my_list_test.go new file mode 100644 index 000000000..9edb28867 --- /dev/null +++ b/ja/codes/go/chapter_array_and_linkedlist/my_list_test.go @@ -0,0 +1,46 @@ +// File: my_list_test.go +// Created Time: 2022-12-18 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_array_and_linkedlist + +import ( + "fmt" + "testing" +) + +/* Driver Code */ +func TestMyList(t *testing.T) { + /* リストを初期化 */ + nums := newMyList() + /* 末尾に要素を追加 */ + nums.add(1) + nums.add(3) + nums.add(2) + nums.add(5) + nums.add(4) + fmt.Printf("リスト nums = %v 、容量 = %v 、長さ = %v\n", nums.toArray(), nums.capacity(), nums.size()) + + /* 中間に要素を挿入 */ + nums.insert(6, 3) + fmt.Printf("インデックス 3 に数字 6 を挿入すると、nums = %v\n", nums.toArray()) + + /* 要素を削除 */ + nums.remove(3) + fmt.Printf("インデックス 3 の要素を削除すると、nums = %v\n", nums.toArray()) + + /* 要素にアクセス */ + num := nums.get(1) + fmt.Printf("インデックス 1 の要素にアクセスすると、num = %v\n", num) + + /* 要素を更新 */ + nums.set(0, 1) + fmt.Printf("インデックス 1 の要素を 0 に更新すると、nums = %v\n", nums.toArray()) + + /* 拡張機構をテストする */ + for i := 0; i < 10; i++ { + // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する + nums.add(i) + } + fmt.Printf("拡張後のリスト nums = %v 、容量 = %v 、長さ = %v\n", nums.toArray(), nums.capacity(), nums.size()) +} diff --git a/ja/codes/go/chapter_backtracking/n_queens.go b/ja/codes/go/chapter_backtracking/n_queens.go new file mode 100644 index 000000000..eb1fec4bf --- /dev/null +++ b/ja/codes/go/chapter_backtracking/n_queens.go @@ -0,0 +1,57 @@ +// File: n_queens.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +/* バックトラッキング:N クイーン */ +func backtrack(row, n int, state *[][]string, res *[][][]string, cols, diags1, diags2 *[]bool) { + // すべての行への配置が完了したら、解を記録する + if row == n { + newState := make([][]string, len(*state)) + for i, _ := range newState { + newState[i] = make([]string, len((*state)[0])) + copy(newState[i], (*state)[i]) + + } + *res = append(*res, newState) + return + } + // すべての列を走査 + for col := 0; col < n; col++ { + // このマスに対応する主対角線と副対角線を計算 + 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, true, true + // 次の行に配置する + backtrack(row+1, n, state, res, cols, diags1, diags2) + // 戻す:そのマスを空きマスに戻す + (*state)[row][col] = "#" + (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = false, false, false + } + } +} + +/* N クイーンを解く */ +func nQueens(n int) [][][]string { + // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す + state := make([][]string, n) + for i := 0; i < n; i++ { + row := make([]string, n) + for i := 0; i < n; i++ { + row[i] = "#" + } + state[i] = row + } + // 列にクイーンがあるか記録 + cols := make([]bool, n) + diags1 := make([]bool, 2*n-1) + diags2 := make([]bool, 2*n-1) + res := make([][][]string, 0) + backtrack(0, n, &state, &res, &cols, &diags1, &diags2) + return res +} diff --git a/ja/codes/go/chapter_backtracking/n_queens_test.go b/ja/codes/go/chapter_backtracking/n_queens_test.go new file mode 100644 index 000000000..ad9636267 --- /dev/null +++ b/ja/codes/go/chapter_backtracking/n_queens_test.go @@ -0,0 +1,24 @@ +// File: n_queens_test.go +// Created Time: 2023-05-14 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + "fmt" + "testing" +) + +func TestNQueens(t *testing.T) { + n := 4 + res := nQueens(n) + + fmt.Println("入力された盤面の縦横は ", n) + fmt.Println("クイーンの配置パターンは ", len(res), " 通り") + for _, state := range res { + fmt.Println("--------------------") + for _, row := range state { + fmt.Println(row) + } + } +} diff --git a/ja/codes/go/chapter_backtracking/permutation_test.go b/ja/codes/go/chapter_backtracking/permutation_test.go new file mode 100644 index 000000000..9181852bd --- /dev/null +++ b/ja/codes/go/chapter_backtracking/permutation_test.go @@ -0,0 +1,33 @@ +// File: permutation_test.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestPermutationI(t *testing.T) { + /* 全順列 I */ + nums := []int{1, 2, 3} + fmt.Printf("入力配列 nums = ") + PrintSlice(nums) + + res := permutationsI(nums) + fmt.Printf("すべての順列 res = ") + fmt.Println(res) +} + +func TestPermutationII(t *testing.T) { + nums := []int{1, 2, 2} + fmt.Printf("入力配列 nums = ") + PrintSlice(nums) + + res := permutationsII(nums) + fmt.Printf("すべての順列 res = ") + fmt.Println(res) +} diff --git a/ja/codes/go/chapter_backtracking/permutations_i.go b/ja/codes/go/chapter_backtracking/permutations_i.go new file mode 100644 index 000000000..27451d3e5 --- /dev/null +++ b/ja/codes/go/chapter_backtracking/permutations_i.go @@ -0,0 +1,38 @@ +// File: permutations_i.go +// Created Time: 2023-05-14 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +/* バックトラッキング:順列 I */ +func backtrackI(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { + // 状態の長さが要素数に等しければ、解を記録 + if len(*state) == len(*choices) { + newState := append([]int{}, *state...) + *res = append(*res, newState) + } + // すべての選択肢を走査 + for i := 0; i < len(*choices); i++ { + choice := (*choices)[i] + // 枝刈り:要素の重複選択を許可しない + if !(*selected)[i] { + // 試行: 選択を行い、状態を更新 + (*selected)[i] = true + *state = append(*state, choice) + // 次の選択へ進む + backtrackI(state, choices, selected, res) + // バックトラック:選択を取り消し、前の状態に戻す + (*selected)[i] = false + *state = (*state)[:len(*state)-1] + } + } +} + +/* 全順列 I */ +func permutationsI(nums []int) [][]int { + res := make([][]int, 0) + state := make([]int, 0) + selected := make([]bool, len(nums)) + backtrackI(&state, &nums, &selected, &res) + return res +} diff --git a/ja/codes/go/chapter_backtracking/permutations_ii.go b/ja/codes/go/chapter_backtracking/permutations_ii.go new file mode 100644 index 000000000..7e52c8cd1 --- /dev/null +++ b/ja/codes/go/chapter_backtracking/permutations_ii.go @@ -0,0 +1,41 @@ +// File: permutations_ii.go +// Created Time: 2023-05-14 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +/* バックトラッキング:順列 II */ +func backtrackII(state *[]int, choices *[]int, selected *[]bool, res *[][]int) { + // 状態の長さが要素数に等しければ、解を記録 + if len(*state) == len(*choices) { + newState := append([]int{}, *state...) + *res = append(*res, newState) + } + // すべての選択肢を走査 + duplicated := make(map[int]struct{}, 0) + for i := 0; i < len(*choices); i++ { + choice := (*choices)[i] + // 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない + if _, ok := duplicated[choice]; !ok && !(*selected)[i] { + // 試す: 選択を行って状態を更新 + // 選択済みの要素値を記録 + duplicated[choice] = struct{}{} + (*selected)[i] = true + *state = append(*state, choice) + // 次の選択へ進む + backtrackII(state, choices, selected, res) + // バックトラック:選択を取り消し、前の状態に戻す + (*selected)[i] = false + *state = (*state)[:len(*state)-1] + } + } +} + +/* 全順列 II */ +func permutationsII(nums []int) [][]int { + res := make([][]int, 0) + state := make([]int, 0) + selected := make([]bool, len(nums)) + backtrackII(&state, &nums, &selected, &res) + return res +} diff --git a/ja/codes/go/chapter_backtracking/preorder_traversal_i_compact.go b/ja/codes/go/chapter_backtracking/preorder_traversal_i_compact.go new file mode 100644 index 000000000..44656a6e3 --- /dev/null +++ b/ja/codes/go/chapter_backtracking/preorder_traversal_i_compact.go @@ -0,0 +1,22 @@ +// File: preorder_traversal_i_compact.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 前順走査:例題 1 */ +func preOrderI(root *TreeNode, res *[]*TreeNode) { + if root == nil { + return + } + if (root.Val).(int) == 7 { + // 解を記録 + *res = append(*res, root) + } + preOrderI(root.Left, res) + preOrderI(root.Right, res) +} diff --git a/ja/codes/go/chapter_backtracking/preorder_traversal_ii_compact.go b/ja/codes/go/chapter_backtracking/preorder_traversal_ii_compact.go new file mode 100644 index 000000000..5f46355e0 --- /dev/null +++ b/ja/codes/go/chapter_backtracking/preorder_traversal_ii_compact.go @@ -0,0 +1,26 @@ +// File: preorder_traversal_ii_compact.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 前順走査:例題 2 */ +func preOrderII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) { + if root == nil { + return + } + // 試す + *path = append(*path, root) + if root.Val.(int) == 7 { + // 解を記録 + *res = append(*res, append([]*TreeNode{}, *path...)) + } + preOrderII(root.Left, res, path) + preOrderII(root.Right, res, path) + // バックトラック + *path = (*path)[:len(*path)-1] +} diff --git a/ja/codes/go/chapter_backtracking/preorder_traversal_iii_compact.go b/ja/codes/go/chapter_backtracking/preorder_traversal_iii_compact.go new file mode 100644 index 000000000..6d8a9e527 --- /dev/null +++ b/ja/codes/go/chapter_backtracking/preorder_traversal_iii_compact.go @@ -0,0 +1,27 @@ +// File: preorder_traversal_iii_compact.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 前順走査:例題 3 */ +func preOrderIII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) { + // 枝刈り + if root == nil || root.Val == 3 { + return + } + // 試す + *path = append(*path, root) + if root.Val.(int) == 7 { + // 解を記録 + *res = append(*res, append([]*TreeNode{}, *path...)) + } + preOrderIII(root.Left, res, path) + preOrderIII(root.Right, res, path) + // バックトラック + *path = (*path)[:len(*path)-1] +} diff --git a/ja/codes/go/chapter_backtracking/preorder_traversal_iii_template.go b/ja/codes/go/chapter_backtracking/preorder_traversal_iii_template.go new file mode 100644 index 000000000..990301c51 --- /dev/null +++ b/ja/codes/go/chapter_backtracking/preorder_traversal_iii_template.go @@ -0,0 +1,57 @@ +// File: preorder_traversal_iii_template.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 現在の状態が解かどうかを判定 */ +func isSolution(state *[]*TreeNode) bool { + return len(*state) != 0 && (*state)[len(*state)-1].Val == 7 +} + +/* 解を記録 */ +func recordSolution(state *[]*TreeNode, res *[][]*TreeNode) { + *res = append(*res, append([]*TreeNode{}, *state...)) +} + +/* 現在の状態で、この選択が有効かどうかを判定 */ +func isValid(state *[]*TreeNode, choice *TreeNode) bool { + return choice != nil && choice.Val != 3 +} + +/* 状態を更新 */ +func makeChoice(state *[]*TreeNode, choice *TreeNode) { + *state = append(*state, choice) +} + +/* 状態を元に戻す */ +func undoChoice(state *[]*TreeNode, choice *TreeNode) { + *state = (*state)[:len(*state)-1] +} + +/* バックトラッキング:例題 3 */ +func backtrackIII(state *[]*TreeNode, choices *[]*TreeNode, res *[][]*TreeNode) { + // 解かどうかを確認 + if isSolution(state) { + // 解を記録 + recordSolution(state, res) + } + // すべての選択肢を走査 + for _, choice := range *choices { + // 枝刈り:選択が妥当かを確認する + if isValid(state, choice) { + // 試行: 選択を行い、状態を更新 + makeChoice(state, choice) + // 次の選択へ進む + temp := make([]*TreeNode, 0) + temp = append(temp, choice.Left, choice.Right) + backtrackIII(state, &temp, res) + // バックトラック:選択を取り消し、前の状態に戻す + undoChoice(state, choice) + } + } +} diff --git a/ja/codes/go/chapter_backtracking/preorder_traversal_test.go b/ja/codes/go/chapter_backtracking/preorder_traversal_test.go new file mode 100644 index 000000000..95a4aadd7 --- /dev/null +++ b/ja/codes/go/chapter_backtracking/preorder_traversal_test.go @@ -0,0 +1,91 @@ +// File: preorder_traversal_i_compact_test.go +// Created Time: 2023-05-09 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestPreorderTraversalICompact(t *testing.T) { + /* 二分木を初期化 */ + root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) + fmt.Println("\n二分木を初期化") + PrintTree(root) + + // 先行順走査 + res := make([]*TreeNode, 0) + preOrderI(root, &res) + + fmt.Println("\n値が 7 のノードをすべて出力") + for _, node := range res { + fmt.Printf("%v ", node.Val) + } + fmt.Println() +} + +func TestPreorderTraversalIICompact(t *testing.T) { + /* 二分木を初期化 */ + root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) + fmt.Println("\n二分木を初期化") + PrintTree(root) + + // 先行順走査 + path := make([]*TreeNode, 0) + res := make([][]*TreeNode, 0) + preOrderII(root, &res, &path) + + fmt.Println("\n根ノードからノード 7 までの経路をすべて出力") + for _, path := range res { + for _, node := range path { + fmt.Printf("%v ", node.Val) + } + fmt.Println() + } +} + +func TestPreorderTraversalIIICompact(t *testing.T) { + /* 二分木を初期化 */ + root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) + fmt.Println("\n二分木を初期化") + PrintTree(root) + + // 先行順走査 + path := make([]*TreeNode, 0) + res := make([][]*TreeNode, 0) + preOrderIII(root, &res, &path) + + fmt.Println("\n根ノードからノード 7 までの経路をすべて出力(経路に値が 3 のノードを含まない)") + for _, path := range res { + for _, node := range path { + fmt.Printf("%v ", node.Val) + } + fmt.Println() + } +} + +func TestPreorderTraversalIIITemplate(t *testing.T) { + /* 二分木を初期化 */ + root := SliceToTree([]any{1, 7, 3, 4, 5, 6, 7}) + fmt.Println("\n二分木を初期化") + PrintTree(root) + + // バックトラッキング法 + res := make([][]*TreeNode, 0) + state := make([]*TreeNode, 0) + choices := make([]*TreeNode, 0) + choices = append(choices, root) + backtrackIII(&state, &choices, &res) + + fmt.Println("\n根ノードからノード 7 までの経路をすべて出力(経路に値が 3 のノードを含まない)") + for _, path := range res { + for _, node := range path { + fmt.Printf("%v ", node.Val) + } + fmt.Println() + } +} diff --git a/ja/codes/go/chapter_backtracking/subset_sum_i.go b/ja/codes/go/chapter_backtracking/subset_sum_i.go new file mode 100644 index 000000000..0bad9f078 --- /dev/null +++ b/ja/codes/go/chapter_backtracking/subset_sum_i.go @@ -0,0 +1,42 @@ +// File: subset_sum_i.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import "sort" + +/* バックトラッキング:部分和 I */ +func backtrackSubsetSumI(start, target int, state, choices *[]int, res *[][]int) { + // 部分集合の和が target に等しければ、解を記録 + if target == 0 { + newState := append([]int{}, *state...) + *res = append(*res, newState) + return + } + // すべての選択肢を走査 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + for i := start; i < len(*choices); i++ { + // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため + if target-(*choices)[i] < 0 { + break + } + // 試す:選択を行い、target と start を更新 + *state = append(*state, (*choices)[i]) + // 次の選択へ進む + backtrackSubsetSumI(i, target-(*choices)[i], state, choices, res) + // バックトラック:選択を取り消し、前の状態に戻す + *state = (*state)[:len(*state)-1] + } +} + +/* 部分和 I を解く */ +func subsetSumI(nums []int, target int) [][]int { + state := make([]int, 0) // 状態(部分集合) + sort.Ints(nums) // nums をソート + start := 0 // 開始点を走査 + res := make([][]int, 0) // 結果リスト(部分集合のリスト) + backtrackSubsetSumI(start, target, &state, &nums, &res) + return res +} diff --git a/ja/codes/go/chapter_backtracking/subset_sum_i_naive.go b/ja/codes/go/chapter_backtracking/subset_sum_i_naive.go new file mode 100644 index 000000000..bb767d18b --- /dev/null +++ b/ja/codes/go/chapter_backtracking/subset_sum_i_naive.go @@ -0,0 +1,37 @@ +// File: subset_sum_i_naive.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +/* バックトラッキング:部分和 I */ +func backtrackSubsetSumINaive(total, target int, state, choices *[]int, res *[][]int) { + // 部分集合の和が target に等しければ、解を記録 + if target == total { + newState := append([]int{}, *state...) + *res = append(*res, newState) + return + } + // すべての選択肢を走査 + for i := 0; i < len(*choices); i++ { + // 枝刈り:部分和が target を超える場合はその選択をスキップする + if total+(*choices)[i] > target { + continue + } + // 試行:選択を行い、要素と total を更新する + *state = append(*state, (*choices)[i]) + // 次の選択へ進む + backtrackSubsetSumINaive(total+(*choices)[i], target, state, choices, res) + // バックトラック:選択を取り消し、前の状態に戻す + *state = (*state)[:len(*state)-1] + } +} + +/* 部分和 I を解く(重複部分集合を含む) */ +func subsetSumINaive(nums []int, target int) [][]int { + state := make([]int, 0) // 状態(部分集合) + total := 0 // 部分和 + res := make([][]int, 0) // 結果リスト(部分集合のリスト) + backtrackSubsetSumINaive(total, target, &state, &nums, &res) + return res +} diff --git a/ja/codes/go/chapter_backtracking/subset_sum_ii.go b/ja/codes/go/chapter_backtracking/subset_sum_ii.go new file mode 100644 index 000000000..3d3f61743 --- /dev/null +++ b/ja/codes/go/chapter_backtracking/subset_sum_ii.go @@ -0,0 +1,47 @@ +// File: subset_sum_ii.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import "sort" + +/* バックトラッキング:部分和 II */ +func backtrackSubsetSumII(start, target int, state, choices *[]int, res *[][]int) { + // 部分集合の和が target に等しければ、解を記録 + if target == 0 { + newState := append([]int{}, *state...) + *res = append(*res, newState) + return + } + // すべての選択肢を走査 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける + for i := start; i < len(*choices); i++ { + // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため + if target-(*choices)[i] < 0 { + break + } + // 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする + if i > start && (*choices)[i] == (*choices)[i-1] { + continue + } + // 試す:選択を行い、target と start を更新 + *state = append(*state, (*choices)[i]) + // 次の選択へ進む + backtrackSubsetSumII(i+1, target-(*choices)[i], state, choices, res) + // バックトラック:選択を取り消し、前の状態に戻す + *state = (*state)[:len(*state)-1] + } +} + +/* 部分和 II を解く */ +func subsetSumII(nums []int, target int) [][]int { + state := make([]int, 0) // 状態(部分集合) + sort.Ints(nums) // nums をソート + start := 0 // 開始点を走査 + res := make([][]int, 0) // 結果リスト(部分集合のリスト) + backtrackSubsetSumII(start, target, &state, &nums, &res) + return res +} diff --git a/ja/codes/go/chapter_backtracking/subset_sum_test.go b/ja/codes/go/chapter_backtracking/subset_sum_test.go new file mode 100644 index 000000000..75ccd7b53 --- /dev/null +++ b/ja/codes/go/chapter_backtracking/subset_sum_test.go @@ -0,0 +1,56 @@ +// File: subset_sum_test.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_backtracking + +import ( + "fmt" + "strconv" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestSubsetSumINaive(t *testing.T) { + nums := []int{3, 4, 5} + target := 9 + res := subsetSumINaive(nums, target) + + fmt.Printf("target = " + strconv.Itoa(target) + ", 入力配列 nums = ") + PrintSlice(nums) + + fmt.Println("合計が " + strconv.Itoa(target) + " に等しい部分集合 res = ") + for i := range res { + PrintSlice(res[i]) + } + fmt.Println("この方法の出力結果には重複した集合が含まれることに注意してください") +} + +func TestSubsetSumI(t *testing.T) { + nums := []int{3, 4, 5} + target := 9 + res := subsetSumI(nums, target) + + fmt.Printf("target = " + strconv.Itoa(target) + ", 入力配列 nums = ") + PrintSlice(nums) + + fmt.Println("合計が " + strconv.Itoa(target) + " に等しい部分集合 res = ") + for i := range res { + PrintSlice(res[i]) + } +} + +func TestSubsetSumII(t *testing.T) { + nums := []int{4, 4, 5} + target := 9 + res := subsetSumII(nums, target) + + fmt.Printf("target = " + strconv.Itoa(target) + ", 入力配列 nums = ") + PrintSlice(nums) + + fmt.Println("合計が " + strconv.Itoa(target) + " に等しい部分集合 res = ") + for i := range res { + PrintSlice(res[i]) + } +} diff --git a/ja/codes/go/chapter_computational_complexity/iteration.go b/ja/codes/go/chapter_computational_complexity/iteration.go new file mode 100644 index 000000000..f763303f0 --- /dev/null +++ b/ja/codes/go/chapter_computational_complexity/iteration.go @@ -0,0 +1,59 @@ +// File: iteration.go +// Created Time: 2023-08-28 +// Author: Reanon (793584285@qq.com) + +package chapter_computational_complexity + +import "fmt" + +/* for ループ */ +func forLoop(n int) int { + res := 0 + // 1, 2, ..., n-1, n を順に加算する + for i := 1; i <= n; i++ { + res += i + } + return res +} + +/* while ループ */ +func whileLoop(n int) int { + res := 0 + // 条件変数を初期化する + i := 1 + // 1, 2, ..., n-1, n を順に加算する + for i <= n { + res += i + // 条件変数を更新する + i++ + } + return res +} + +/* while ループ(2回更新) */ +func whileLoopII(n int) int { + res := 0 + // 条件変数を初期化する + i := 1 + // 1, 4, 10, ... を順に加算する + for i <= n { + res += i + // 条件変数を更新する + i++ + i *= 2 + } + return res +} + +/* 二重 for ループ */ +func nestedForLoop(n int) string { + res := "" + // i = 1, 2, ..., n-1, n とループする + for i := 1; i <= n; i++ { + for j := 1; j <= n; j++ { + // j = 1, 2, ..., n-1, n とループする + res += fmt.Sprintf("(%d, %d), ", i, j) + } + } + return res +} diff --git a/ja/codes/go/chapter_computational_complexity/iteration_test.go b/ja/codes/go/chapter_computational_complexity/iteration_test.go new file mode 100644 index 000000000..efd49489e --- /dev/null +++ b/ja/codes/go/chapter_computational_complexity/iteration_test.go @@ -0,0 +1,26 @@ +// File: iteration_test.go +// Created Time: 2023-08-28 +// Author: Reanon (793584285@qq.com) + +package chapter_computational_complexity + +import ( + "fmt" + "testing" +) + +/* Driver Code */ +func TestIteration(t *testing.T) { + n := 5 + res := forLoop(n) + fmt.Println("\nfor ループの合計結果 res = ", res) + + res = whileLoop(n) + fmt.Println("\nwhile ループの合計結果 res = ", res) + + res = whileLoopII(n) + fmt.Println("\nwhile ループ(2 回更新)の合計結果 res = ", res) + + resStr := nestedForLoop(n) + fmt.Println("\n二重 for ループの走査結果 ", resStr) +} diff --git a/ja/codes/go/chapter_computational_complexity/recursion.go b/ja/codes/go/chapter_computational_complexity/recursion.go new file mode 100644 index 000000000..75ef0099b --- /dev/null +++ b/ja/codes/go/chapter_computational_complexity/recursion.go @@ -0,0 +1,61 @@ +// File: recursion.go +// Created Time: 2023-08-28 +// Author: Reanon (793584285@qq.com) + +package chapter_computational_complexity + +import "container/list" + +/* 再帰 */ +func recur(n int) int { + // 終了条件 + if n == 1 { + return 1 + } + // 再帰:再帰呼び出し + res := recur(n - 1) + // 帰りがけ:結果を返す + return n + res +} + +/* 反復で再帰を模擬する */ +func forLoopRecur(n int) int { + // 明示的なスタックを使ってシステムコールスタックを模擬する + stack := list.New() + res := 0 + // 再帰:再帰呼び出し + for i := n; i > 0; i-- { + // 「スタックへのプッシュ」で「再帰」を模擬する + stack.PushBack(i) + } + // 帰りがけ:結果を返す + for stack.Len() != 0 { + // 「スタックから取り出す操作」で「帰り」をシミュレート + res += stack.Back().Value.(int) + stack.Remove(stack.Back()) + } + // res = 1+2+3+...+n + return res +} + +/* 末尾再帰 */ +func tailRecur(n int, res int) int { + // 終了条件 + if n == 0 { + return res + } + // 末尾再帰呼び出し + return tailRecur(n-1, res+n) +} + +/* フィボナッチ数列:再帰 */ +func fib(n int) int { + // 終了条件 f(1) = 0, f(2) = 1 + if n == 1 || n == 2 { + return n - 1 + } + // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す + res := fib(n-1) + fib(n-2) + // 結果 f(n) を返す + return res +} diff --git a/ja/codes/go/chapter_computational_complexity/recursion_test.go b/ja/codes/go/chapter_computational_complexity/recursion_test.go new file mode 100644 index 000000000..a29e20fd6 --- /dev/null +++ b/ja/codes/go/chapter_computational_complexity/recursion_test.go @@ -0,0 +1,26 @@ +// File: recursion_test.go +// Created Time: 2023-08-28 +// Author: Reanon (793584285@qq.com) + +package chapter_computational_complexity + +import ( + "fmt" + "testing" +) + +/* Driver Code */ +func TestRecursion(t *testing.T) { + n := 5 + res := recur(n) + fmt.Println("\n再帰関数の合計結果 res = ", res) + + res = forLoopRecur(n) + fmt.Println("\n反復で再帰をシミュレートした合計結果 res = ", res) + + res = tailRecur(n, 0) + fmt.Println("\n末尾再帰関数の合計結果 res = ", res) + + res = fib(n) + fmt.Println("\nフィボナッチ数列の第", n, "項は", res) +} diff --git a/ja/codes/go/chapter_computational_complexity/space_complexity.go b/ja/codes/go/chapter_computational_complexity/space_complexity.go new file mode 100644 index 000000000..169eababf --- /dev/null +++ b/ja/codes/go/chapter_computational_complexity/space_complexity.go @@ -0,0 +1,106 @@ +// File: space_complexity.go +// Created Time: 2022-12-15 +// Author: cathay (cathaycchen@gmail.com) + +package chapter_computational_complexity + +import ( + "fmt" + "strconv" + + . "github.com/krahets/hello-algo/pkg" +) + +/* 構造体 */ +type node struct { + val int + next *node +} + +/* node 構造体を作成する */ +func newNode(val int) *node { + return &node{val: val} +} + +/* 関数 */ +func function() int { + // いくつかの操作を実行... + return 0 +} + +/* 定数階 */ +func spaceConstant(n int) { + // 定数、変数、オブジェクトは O(1) の空間を占める + const a = 0 + b := 0 + nums := make([]int, 10000) + node := newNode(0) + // ループ内の変数は O(1) の空間を占める + var c int + for i := 0; i < n; i++ { + c = 0 + } + // ループ内の関数は O(1) の空間を占める + for i := 0; i < n; i++ { + function() + } + b += 0 + c += 0 + nums[0] = 0 + node.val = 0 +} + +/* 線形階 */ +func spaceLinear(n int) { + // 長さ n の配列は O(n) の空間を使用 + _ = make([]int, n) + // 長さ n のリストは O(n) の空間を使用 + var nodes []*node + for i := 0; i < n; i++ { + nodes = append(nodes, newNode(i)) + } + // 長さ n のハッシュテーブルは O(n) の空間を使用 + m := make(map[int]string, n) + for i := 0; i < n; i++ { + m[i] = strconv.Itoa(i) + } +} + +/* 線形時間(再帰実装) */ +func spaceLinearRecur(n int) { + fmt.Println("再帰 n =", n) + if n == 1 { + return + } + spaceLinearRecur(n - 1) +} + +/* 二乗階 */ +func spaceQuadratic(n int) { + // 行列は O(n^2) の空間を使用する + numMatrix := make([][]int, n) + for i := 0; i < n; i++ { + numMatrix[i] = make([]int, n) + } +} + +/* 二次時間(再帰実装) */ +func spaceQuadraticRecur(n int) int { + if n <= 0 { + return 0 + } + nums := make([]int, n) + fmt.Printf("再帰 n = %d における nums の長さ = %d \n", n, len(nums)) + return spaceQuadraticRecur(n - 1) +} + +/* 指数時間(完全二分木の構築) */ +func buildTree(n int) *TreeNode { + if n == 0 { + return nil + } + root := NewTreeNode(0) + root.Left = buildTree(n - 1) + root.Right = buildTree(n - 1) + return root +} diff --git a/ja/codes/go/chapter_computational_complexity/space_complexity_test.go b/ja/codes/go/chapter_computational_complexity/space_complexity_test.go new file mode 100644 index 000000000..207f08722 --- /dev/null +++ b/ja/codes/go/chapter_computational_complexity/space_complexity_test.go @@ -0,0 +1,26 @@ +// File: space_complexity_test.go +// Created Time: 2022-12-15 +// Author: cathay (cathaycchen@gmail.com) + +package chapter_computational_complexity + +import ( + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestSpaceComplexity(t *testing.T) { + n := 5 + // 定数階 + spaceConstant(n) + // 線形階 + spaceLinear(n) + spaceLinearRecur(n) + // 二乗階 + spaceQuadratic(n) + spaceQuadraticRecur(n) + // 指数オーダー + root := buildTree(n) + PrintTree(root) +} diff --git a/ja/codes/go/chapter_computational_complexity/time_complexity.go b/ja/codes/go/chapter_computational_complexity/time_complexity.go new file mode 100644 index 000000000..cc427f224 --- /dev/null +++ b/ja/codes/go/chapter_computational_complexity/time_complexity.go @@ -0,0 +1,130 @@ +// File: time_complexity.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_computational_complexity + +/* 定数階 */ +func constant(n int) int { + count := 0 + size := 100000 + for i := 0; i < size; i++ { + count++ + } + return count +} + +/* 線形階 */ +func linear(n int) int { + count := 0 + for i := 0; i < n; i++ { + count++ + } + return count +} + +/* 線形時間(配列を走査) */ +func arrayTraversal(nums []int) int { + count := 0 + // ループ回数は配列長に比例する + for range nums { + count++ + } + return count +} + +/* 二乗階 */ +func quadratic(n int) int { + count := 0 + // ループ回数はデータサイズ n の二乗に比例する + for i := 0; i < n; i++ { + for j := 0; j < n; j++ { + count++ + } + } + return count +} + +/* 二次時間(バブルソート) */ +func bubbleSort(nums []int) int { + count := 0 // カウンタ + // 外側のループ:未ソート区間は [0, i] + for i := len(nums) - 1; i > 0; i-- { + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for j := 0; j < i; j++ { + if nums[j] > nums[j+1] { + // nums[j] と nums[j + 1] を交換 + tmp := nums[j] + nums[j] = nums[j+1] + nums[j+1] = tmp + count += 3 // 要素交換には 3 回の単位操作が含まれる + } + } + } + return count +} + +/* 指数時間(ループ実装) */ +func exponential(n int) int { + count, base := 0, 1 + // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する + for i := 0; i < n; i++ { + for j := 0; j < base; j++ { + count++ + } + base *= 2 + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count +} + +/* 指数時間(再帰実装) */ +func expRecur(n int) int { + if n == 1 { + return 1 + } + return expRecur(n-1) + expRecur(n-1) + 1 +} + +/* 対数時間(ループ実装) */ +func logarithmic(n int) int { + count := 0 + for n > 1 { + n = n / 2 + count++ + } + return count +} + +/* 対数時間(再帰実装) */ +func logRecur(n int) int { + if n <= 1 { + return 0 + } + return logRecur(n/2) + 1 +} + +/* 線形対数時間 */ +func linearLogRecur(n int) int { + if n <= 1 { + return 1 + } + count := linearLogRecur(n/2) + linearLogRecur(n/2) + for i := 0; i < n; i++ { + count++ + } + return count +} + +/* 階乗時間(再帰実装) */ +func factorialRecur(n int) int { + if n == 0 { + return 1 + } + count := 0 + // 1個から n 個に分裂 + for i := 0; i < n; i++ { + count += factorialRecur(n - 1) + } + return count +} diff --git a/ja/codes/go/chapter_computational_complexity/time_complexity_test.go b/ja/codes/go/chapter_computational_complexity/time_complexity_test.go new file mode 100644 index 000000000..32c71603f --- /dev/null +++ b/ja/codes/go/chapter_computational_complexity/time_complexity_test.go @@ -0,0 +1,48 @@ +// File: time_complexity_test.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_computational_complexity + +import ( + "fmt" + "testing" +) + +func TestTimeComplexity(t *testing.T) { + n := 8 + fmt.Println("入力データサイズ n =", n) + + count := constant(n) + fmt.Println("定数時間の操作回数 =", count) + + count = linear(n) + fmt.Println("線形時間の操作回数 =", count) + count = arrayTraversal(make([]int, n)) + fmt.Println("線形時間(配列走査)の操作回数 =", count) + + count = quadratic(n) + fmt.Println("二乗時間の操作回数 =", count) + nums := make([]int, n) + for i := 0; i < n; i++ { + nums[i] = n - i + } + count = bubbleSort(nums) + fmt.Println("二乗時間(バブルソート)の操作回数 =", count) + + count = exponential(n) + fmt.Println("指数時間(ループ実装)の操作回数 =", count) + count = expRecur(n) + fmt.Println("指数時間(再帰実装)の操作回数 =", count) + + count = logarithmic(n) + fmt.Println("対数時間(ループ実装)の操作回数 =", count) + count = logRecur(n) + fmt.Println("対数時間(再帰実装)の操作回数 =", count) + + count = linearLogRecur(n) + fmt.Println("線形対数時間(再帰実装)の操作回数 =", count) + + count = factorialRecur(n) + fmt.Println("階乗時間(再帰実装)の操作回数 =", count) +} diff --git a/ja/codes/go/chapter_computational_complexity/worst_best_time_complexity.go b/ja/codes/go/chapter_computational_complexity/worst_best_time_complexity.go new file mode 100644 index 000000000..09427afc2 --- /dev/null +++ b/ja/codes/go/chapter_computational_complexity/worst_best_time_complexity.go @@ -0,0 +1,35 @@ +// File: worst_best_time_complexity.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com), cathay (cathaycchen@gmail.com) + +package chapter_computational_complexity + +import ( + "math/rand" +) + +/* 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 */ +func randomNumbers(n int) []int { + nums := make([]int, n) + // 配列 nums = { 1, 2, 3, ..., n } を生成 + for i := 0; i < n; i++ { + nums[i] = i + 1 + } + // 配列要素をランダムにシャッフル + rand.Shuffle(len(nums), func(i, j int) { + nums[i], nums[j] = nums[j], nums[i] + }) + return nums +} + +/* 配列 nums 内で数値 1 のインデックスを探す */ +func findOne(nums []int) int { + for i := 0; i < len(nums); i++ { + // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる + // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる + if nums[i] == 1 { + return i + } + } + return -1 +} diff --git a/ja/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go b/ja/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go new file mode 100644 index 000000000..ab0855633 --- /dev/null +++ b/ja/codes/go/chapter_computational_complexity/worst_best_time_complexity_test.go @@ -0,0 +1,20 @@ +// File: worst_best_time_complexity_test.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com), cathay (cathaycchen@gmail.com) + +package chapter_computational_complexity + +import ( + "fmt" + "testing" +) + +func TestWorstBestTimeComplexity(t *testing.T) { + for i := 0; i < 10; i++ { + n := 100 + nums := randomNumbers(n) + index := findOne(nums) + fmt.Println("\n配列 [ 1, 2, ..., n ] をシャッフルした後 =", nums) + fmt.Println("数字 1 のインデックスは", index) + } +} diff --git a/ja/codes/go/chapter_divide_and_conquer/binary_search_recur.go b/ja/codes/go/chapter_divide_and_conquer/binary_search_recur.go new file mode 100644 index 000000000..a5a639659 --- /dev/null +++ b/ja/codes/go/chapter_divide_and_conquer/binary_search_recur.go @@ -0,0 +1,34 @@ +// File: binary_search_recur.go +// Created Time: 2023-07-19 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +/* 二分探索:問題 f(i, j) */ +func dfs(nums []int, target, i, j int) int { + // 区間が空なら対象要素は存在しないため、-1 を返す + if i > j { + return -1 + } + // 中点インデックスを計算する + m := i + ((j - i) >> 1) + // 中点の要素と目標要素の大小を判定する + if nums[m] < target { + // 小さければ右半分の配列を再帰 + // 部分問題 f(m+1, j) を解く + return dfs(nums, target, m+1, j) + } else if nums[m] > target { + // 大きければ左半分の配列を再帰 + // 部分問題 f(i, m-1) を解く + return dfs(nums, target, i, m-1) + } else { + // 目標要素が見つかったらそのインデックスを返す + return m + } +} + +/* 二分探索 */ +func binarySearch(nums []int, target int) int { + n := len(nums) + return dfs(nums, target, 0, n-1) +} diff --git a/ja/codes/go/chapter_divide_and_conquer/binary_search_recur_test.go b/ja/codes/go/chapter_divide_and_conquer/binary_search_recur_test.go new file mode 100644 index 000000000..fdae3877f --- /dev/null +++ b/ja/codes/go/chapter_divide_and_conquer/binary_search_recur_test.go @@ -0,0 +1,20 @@ +// File: binary_search_recur_test.go +// Created Time: 2023-07-19 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import ( + "fmt" + "testing" +) + +func TestBinarySearch(t *testing.T) { + nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} + target := 6 + noTarget := 99 + targetIndex := binarySearch(nums, target) + fmt.Println("対象要素 6 のインデックス = ", targetIndex) + noTargetIndex := binarySearch(nums, noTarget) + fmt.Println("対象要素が存在しないときのインデックス = ", noTargetIndex) +} diff --git a/ja/codes/go/chapter_divide_and_conquer/build_tree.go b/ja/codes/go/chapter_divide_and_conquer/build_tree.go new file mode 100644 index 000000000..fc3f75fcd --- /dev/null +++ b/ja/codes/go/chapter_divide_and_conquer/build_tree.go @@ -0,0 +1,37 @@ +// File: build_tree.go +// Created Time: 2023-07-20 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import . "github.com/krahets/hello-algo/pkg" + +/* 二分木を構築:分割統治 */ +func dfsBuildTree(preorder []int, inorderMap map[int]int, i, l, r int) *TreeNode { + // 部分木区間が空なら終了する + if r-l < 0 { + return nil + } + // ルートノードを初期化する + root := NewTreeNode(preorder[i]) + // m を求めて左右部分木を分割する + m := inorderMap[preorder[i]] + // 部分問題:左部分木を構築する + root.Left = dfsBuildTree(preorder, inorderMap, i+1, l, m-1) + // 部分問題:右部分木を構築する + root.Right = dfsBuildTree(preorder, inorderMap, i+1+m-l, m+1, r) + // 根ノードを返す + return root +} + +/* 二分木を構築 */ +func buildTree(preorder, inorder []int) *TreeNode { + // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する + inorderMap := make(map[int]int, len(inorder)) + for i := 0; i < len(inorder); i++ { + inorderMap[inorder[i]] = i + } + + root := dfsBuildTree(preorder, inorderMap, 0, 0, len(inorder)-1) + return root +} diff --git a/ja/codes/go/chapter_divide_and_conquer/build_tree_test.go b/ja/codes/go/chapter_divide_and_conquer/build_tree_test.go new file mode 100644 index 000000000..f2db074ed --- /dev/null +++ b/ja/codes/go/chapter_divide_and_conquer/build_tree_test.go @@ -0,0 +1,25 @@ +// File: build_tree_test.go +// Created Time: 2023-07-20 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestBuildTree(t *testing.T) { + preorder := []int{3, 9, 2, 1, 7} + inorder := []int{9, 3, 1, 2, 7} + fmt.Print("前順走査 = ") + PrintSlice(preorder) + fmt.Print("中順走査 = ") + PrintSlice(inorder) + + root := buildTree(preorder, inorder) + fmt.Println("構築した二分木は:") + PrintTree(root) +} diff --git a/ja/codes/go/chapter_divide_and_conquer/hanota.go b/ja/codes/go/chapter_divide_and_conquer/hanota.go new file mode 100644 index 000000000..9e5665b7f --- /dev/null +++ b/ja/codes/go/chapter_divide_and_conquer/hanota.go @@ -0,0 +1,39 @@ +// File: hanota.go +// Created Time: 2023-07-21 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import "container/list" + +/* 円盤を 1 枚移動 */ +func move(src, tar *list.List) { + // src の上から円盤を1枚取り出す + pan := src.Back() + // 円盤を tar の上に置く + tar.PushBack(pan.Value) + // `src` の最上部の円盤を取り外す + src.Remove(pan) +} + +/* ハノイの塔の問題 f(i) を解く */ +func dfsHanota(i int, src, buf, tar *list.List) { + // src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す + if i == 1 { + move(src, tar) + return + } + // 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す + dfsHanota(i-1, src, tar, buf) + // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す + move(src, tar) + // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す + dfsHanota(i-1, buf, src, tar) +} + +/* ハノイの塔を解く */ +func solveHanota(A, B, C *list.List) { + n := A.Len() + // A の上から n 枚の円盤を B を介して C へ移す + dfsHanota(n, A, B, C) +} diff --git a/ja/codes/go/chapter_divide_and_conquer/hanota_test.go b/ja/codes/go/chapter_divide_and_conquer/hanota_test.go new file mode 100644 index 000000000..2c614c742 --- /dev/null +++ b/ja/codes/go/chapter_divide_and_conquer/hanota_test.go @@ -0,0 +1,40 @@ +// File: hanota_test.go +// Created Time: 2023-07-21 +// Author: hongyun-robot (1836017030@qq.com) + +package chapter_divide_and_conquer + +import ( + "container/list" + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestHanota(t *testing.T) { + // リスト末尾が柱の頂上 + A := list.New() + for i := 5; i > 0; i-- { + A.PushBack(i) + } + B := list.New() + C := list.New() + fmt.Println("初期状態:") + fmt.Print("A = ") + PrintList(A) + fmt.Print("B = ") + PrintList(B) + fmt.Print("C = ") + PrintList(C) + + solveHanota(A, B, C) + + fmt.Println("円盤の移動完了後:") + fmt.Print("A = ") + PrintList(A) + fmt.Print("B = ") + PrintList(B) + fmt.Print("C = ") + PrintList(C) +} diff --git a/ja/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go b/ja/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go new file mode 100644 index 000000000..78c028370 --- /dev/null +++ b/ja/codes/go/chapter_dynamic_programming/climbing_stairs_backtrack.go @@ -0,0 +1,36 @@ +// File: climbing_stairs_backtrack.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* バックトラッキング */ +func backtrack(choices []int, state, n int, res []int) { + // 第 n 段に到達したら、方法数を 1 増やす + if state == n { + res[0] = res[0] + 1 + } + // すべての選択肢を走査 + for _, choice := range choices { + // 枝刈り: 第 n 段を超えないようにする + if state+choice > n { + continue + } + // 試行: 選択を行い、状態を更新 + backtrack(choices, state+choice, n, res) + // バックトラック + } +} + +/* 階段登り:バックトラッキング */ +func climbingStairsBacktrack(n int) int { + // 1 段または 2 段上ることを選べる + choices := []int{1, 2} + // 第 0 段から上り始める + state := 0 + res := make([]int, 1) + // res[0] を使って方法数を記録する + res[0] = 0 + backtrack(choices, state, n, res) + return res[0] +} diff --git a/ja/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go b/ja/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go new file mode 100644 index 000000000..d8892fafb --- /dev/null +++ b/ja/codes/go/chapter_dynamic_programming/climbing_stairs_constraint_dp.go @@ -0,0 +1,25 @@ +// File: climbing_stairs_constraint_dp.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 制約付き階段登り:動的計画法 */ +func climbingStairsConstraintDP(n int) int { + if n == 1 || n == 2 { + return 1 + } + // 部分問題の解を保存するために dp テーブルを初期化 + dp := make([][3]int, n+1) + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1][1] = 1 + dp[1][2] = 0 + dp[2][1] = 0 + dp[2][2] = 1 + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for i := 3; i <= n; i++ { + dp[i][1] = dp[i-1][2] + dp[i][2] = dp[i-2][1] + dp[i-2][2] + } + return dp[n][1] + dp[n][2] +} diff --git a/ja/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go b/ja/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go new file mode 100644 index 000000000..c88824740 --- /dev/null +++ b/ja/codes/go/chapter_dynamic_programming/climbing_stairs_dfs.go @@ -0,0 +1,21 @@ +// File: climbing_stairs_dfs.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 検索 */ +func dfs(i int) int { + // dp[1] と dp[2] は既知なので返す + if i == 1 || i == 2 { + return i + } + // dp[i] = dp[i-1] + dp[i-2] + count := dfs(i-1) + dfs(i-2) + return count +} + +/* 階段登り:探索 */ +func climbingStairsDFS(n int) int { + return dfs(n) +} diff --git a/ja/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go b/ja/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go new file mode 100644 index 000000000..9467032a4 --- /dev/null +++ b/ja/codes/go/chapter_dynamic_programming/climbing_stairs_dfs_mem.go @@ -0,0 +1,32 @@ +// File: climbing_stairs_dfs_mem.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* メモ化探索 */ +func dfsMem(i int, mem []int) int { + // dp[1] と dp[2] は既知なので返す + if i == 1 || i == 2 { + return i + } + // dp[i] の記録があれば、それをそのまま返す + if mem[i] != -1 { + return mem[i] + } + // dp[i] = dp[i-1] + dp[i-2] + count := dfsMem(i-1, mem) + dfsMem(i-2, mem) + // dp[i] を記録する + mem[i] = count + return count +} + +/* 階段登り:メモ化探索 */ +func climbingStairsDFSMem(n int) int { + // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す + mem := make([]int, n+1) + for i := range mem { + mem[i] = -1 + } + return dfsMem(n, mem) +} diff --git a/ja/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go b/ja/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go new file mode 100644 index 000000000..df74e211a --- /dev/null +++ b/ja/codes/go/chapter_dynamic_programming/climbing_stairs_dp.go @@ -0,0 +1,35 @@ +// File: climbing_stairs_dp.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 階段登り:動的計画法 */ +func climbingStairsDP(n int) int { + if n == 1 || n == 2 { + return n + } + // 部分問題の解を保存するために dp テーブルを初期化 + dp := make([]int, n+1) + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1] = 1 + dp[2] = 2 + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for i := 3; i <= n; i++ { + dp[i] = dp[i-1] + dp[i-2] + } + return dp[n] +} + +/* 階段登り:空間最適化した動的計画法 */ +func climbingStairsDPComp(n int) int { + if n == 1 || n == 2 { + return n + } + a, b := 1, 2 + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for i := 3; i <= n; i++ { + a, b = b, a+b + } + return b +} diff --git a/ja/codes/go/chapter_dynamic_programming/climbing_stairs_test.go b/ja/codes/go/chapter_dynamic_programming/climbing_stairs_test.go new file mode 100644 index 000000000..1d14fb8ed --- /dev/null +++ b/ja/codes/go/chapter_dynamic_programming/climbing_stairs_test.go @@ -0,0 +1,57 @@ +// File: climbing_stairs_test.go +// Created Time: 2023-07-18 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestClimbingStairsBacktrack(t *testing.T) { + n := 9 + res := climbingStairsBacktrack(n) + fmt.Printf("%d 段の階段を登る方法は %d 通り\n", n, res) +} + +func TestClimbingStairsDFS(t *testing.T) { + n := 9 + res := climbingStairsDFS(n) + fmt.Printf("%d 段の階段を登る方法は %d 通り\n", n, res) +} + +func TestClimbingStairsDFSMem(t *testing.T) { + n := 9 + res := climbingStairsDFSMem(n) + fmt.Printf("%d 段の階段を登る方法は %d 通り\n", n, res) +} + +func TestClimbingStairsDP(t *testing.T) { + n := 9 + res := climbingStairsDP(n) + fmt.Printf("%d 段の階段を登る方法は %d 通り\n", n, res) +} + +func TestClimbingStairsDPComp(t *testing.T) { + n := 9 + res := climbingStairsDPComp(n) + fmt.Printf("%d 段の階段を登る方法は %d 通り\n", n, res) +} + +func TestClimbingStairsConstraintDP(t *testing.T) { + n := 9 + res := climbingStairsConstraintDP(n) + fmt.Printf("%d 段の階段を登る方法は %d 通り\n", n, res) +} + +func TestMinCostClimbingStairsDPComp(t *testing.T) { + cost := []int{0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1} + fmt.Printf("入力された階段のコストリストは %v\n", cost) + + res := minCostClimbingStairsDP(cost) + fmt.Printf("階段を登り切る最小コストは %d\n", res) + + res = minCostClimbingStairsDPComp(cost) + fmt.Printf("階段を登り切る最小コストは %d\n", res) +} diff --git a/ja/codes/go/chapter_dynamic_programming/coin_change.go b/ja/codes/go/chapter_dynamic_programming/coin_change.go new file mode 100644 index 000000000..cdc24dce6 --- /dev/null +++ b/ja/codes/go/chapter_dynamic_programming/coin_change.go @@ -0,0 +1,66 @@ +// File: coin_change.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* コイン両替:動的計画法 */ +func coinChangeDP(coins []int, amt int) int { + n := len(coins) + max := amt + 1 + // dp テーブルを初期化 + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, amt+1) + } + // 状態遷移:先頭行と先頭列 + for a := 1; a <= amt; a++ { + dp[0][a] = max + } + // 状態遷移: 残りの行と列 + for i := 1; i <= n; i++ { + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // 目標金額を超えるなら硬貨 i は選ばない + dp[i][a] = dp[i-1][a] + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[i][a] = int(math.Min(float64(dp[i-1][a]), float64(dp[i][a-coins[i-1]]+1))) + } + } + } + if dp[n][amt] != max { + return dp[n][amt] + } + return -1 +} + +/* コイン両替:動的計画法 */ +func coinChangeDPComp(coins []int, amt int) int { + n := len(coins) + max := amt + 1 + // dp テーブルを初期化 + dp := make([]int, amt+1) + for i := 1; i <= amt; i++ { + dp[i] = max + } + // 状態遷移 + for i := 1; i <= n; i++ { + // 順方向に走査する + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a] + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[a] = int(math.Min(float64(dp[a]), float64(dp[a-coins[i-1]]+1))) + } + } + } + if dp[amt] != max { + return dp[amt] + } + return -1 +} diff --git a/ja/codes/go/chapter_dynamic_programming/coin_change_ii.go b/ja/codes/go/chapter_dynamic_programming/coin_change_ii.go new file mode 100644 index 000000000..28bae93b2 --- /dev/null +++ b/ja/codes/go/chapter_dynamic_programming/coin_change_ii.go @@ -0,0 +1,54 @@ +// File: coin_change_ii.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* コイン両替 II:動的計画法 */ +func coinChangeIIDP(coins []int, amt int) int { + n := len(coins) + // dp テーブルを初期化 + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, amt+1) + } + // 先頭列を初期化する + for i := 0; i <= n; i++ { + dp[i][0] = 1 + } + // 状態遷移: 残りの行と列 + for i := 1; i <= n; i++ { + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // 目標金額を超えるなら硬貨 i は選ばない + dp[i][a] = dp[i-1][a] + } else { + // コイン i を選ばない場合と選ぶ場合の和 + dp[i][a] = dp[i-1][a] + dp[i][a-coins[i-1]] + } + } + } + return dp[n][amt] +} + +/* コイン両替 II:空間最適化した動的計画法 */ +func coinChangeIIDPComp(coins []int, amt int) int { + n := len(coins) + // dp テーブルを初期化 + dp := make([]int, amt+1) + dp[0] = 1 + // 状態遷移 + for i := 1; i <= n; i++ { + // 順方向に走査する + for a := 1; a <= amt; a++ { + if coins[i-1] > a { + // 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a] + } else { + // コイン i を選ばない場合と選ぶ場合の和 + dp[a] = dp[a] + dp[a-coins[i-1]] + } + } + } + return dp[amt] +} diff --git a/ja/codes/go/chapter_dynamic_programming/coin_change_test.go b/ja/codes/go/chapter_dynamic_programming/coin_change_test.go new file mode 100644 index 000000000..b15631047 --- /dev/null +++ b/ja/codes/go/chapter_dynamic_programming/coin_change_test.go @@ -0,0 +1,23 @@ +// File: coin_change_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestCoinChange(t *testing.T) { + coins := []int{1, 2, 5} + amt := 4 + + // 動的計画法 + res := coinChangeDP(coins, amt) + fmt.Printf("目標金額を作るのに必要な最小硬貨枚数は %d\n", res) + + // 空間最適化後の動的計画法 + res = coinChangeDPComp(coins, amt) + fmt.Printf("目標金額を作るのに必要な最小硬貨枚数は %d\n", res) +} diff --git a/ja/codes/go/chapter_dynamic_programming/edit_distance.go b/ja/codes/go/chapter_dynamic_programming/edit_distance.go new file mode 100644 index 000000000..cf4cf1499 --- /dev/null +++ b/ja/codes/go/chapter_dynamic_programming/edit_distance.go @@ -0,0 +1,129 @@ +// File: edit_distance.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 編集距離:総当たり探索 */ +func editDistanceDFS(s string, t string, i int, j int) int { + // s と t がともに空なら 0 を返す + if i == 0 && j == 0 { + return 0 + } + // s が空なら t の長さを返す + if i == 0 { + return j + } + // t が空なら s の長さを返す + if j == 0 { + return i + } + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if s[i-1] == t[j-1] { + return editDistanceDFS(s, t, i-1, j-1) + } + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + insert := editDistanceDFS(s, t, i, j-1) + deleted := editDistanceDFS(s, t, i-1, j) + replace := editDistanceDFS(s, t, i-1, j-1) + // 最小編集回数を返す + return MinInt(MinInt(insert, deleted), replace) + 1 +} + +/* 編集距離:メモ化探索 */ +func editDistanceDFSMem(s string, t string, mem [][]int, i int, j int) int { + // s と t がともに空なら 0 を返す + if i == 0 && j == 0 { + return 0 + } + // s が空なら t の長さを返す + if i == 0 { + return j + } + // t が空なら s の長さを返す + if j == 0 { + return i + } + // 記録済みなら、それをそのまま返す + if mem[i][j] != -1 { + return mem[i][j] + } + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if s[i-1] == t[j-1] { + return editDistanceDFSMem(s, t, mem, i-1, j-1) + } + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + insert := editDistanceDFSMem(s, t, mem, i, j-1) + deleted := editDistanceDFSMem(s, t, mem, i-1, j) + replace := editDistanceDFSMem(s, t, mem, i-1, j-1) + // 最小編集回数を記録して返す + mem[i][j] = MinInt(MinInt(insert, deleted), replace) + 1 + return mem[i][j] +} + +/* 編集距離:動的計画法 */ +func editDistanceDP(s string, t string) int { + n := len(s) + m := len(t) + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, m+1) + } + // 状態遷移:先頭行と先頭列 + for i := 1; i <= n; i++ { + dp[i][0] = i + } + for j := 1; j <= m; j++ { + dp[0][j] = j + } + // 状態遷移: 残りの行と列 + for i := 1; i <= n; i++ { + for j := 1; j <= m; j++ { + if s[i-1] == t[j-1] { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[i][j] = dp[i-1][j-1] + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[i][j] = MinInt(MinInt(dp[i][j-1], dp[i-1][j]), dp[i-1][j-1]) + 1 + } + } + } + return dp[n][m] +} + +/* 編集距離:空間最適化した動的計画法 */ +func editDistanceDPComp(s string, t string) int { + n := len(s) + m := len(t) + dp := make([]int, m+1) + // 状態遷移:先頭行 + for j := 1; j <= m; j++ { + dp[j] = j + } + // 状態遷移:残りの行 + for i := 1; i <= n; i++ { + // 状態遷移:先頭列 + leftUp := dp[0] // dp[i-1, j-1] を一時保存する + dp[0] = i + // 状態遷移:残りの列 + for j := 1; j <= m; j++ { + temp := dp[j] + if s[i-1] == t[j-1] { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[j] = leftUp + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[j] = MinInt(MinInt(dp[j-1], dp[j]), leftUp) + 1 + } + leftUp = temp // 次の反復の dp[i-1, j-1] に更新する + } + } + return dp[m] +} + +func MinInt(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/ja/codes/go/chapter_dynamic_programming/edit_distance_test.go b/ja/codes/go/chapter_dynamic_programming/edit_distance_test.go new file mode 100644 index 000000000..6613057eb --- /dev/null +++ b/ja/codes/go/chapter_dynamic_programming/edit_distance_test.go @@ -0,0 +1,40 @@ +// File: edit_distance_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestEditDistanceDFS(test *testing.T) { + s := "bag" + t := "pack" + n := len(s) + m := len(t) + + // 全探索 + res := editDistanceDFS(s, t, n, m) + fmt.Printf("%s を %s に変更するには最低 %d 回の編集が必要です\n", s, t, res) + + // メモ化探索 + mem := make([][]int, n+1) + for i := 0; i <= n; i++ { + mem[i] = make([]int, m+1) + for j := 0; j <= m; j++ { + mem[i][j] = -1 + } + } + res = editDistanceDFSMem(s, t, mem, n, m) + fmt.Printf("%s を %s に変更するには最低 %d 回の編集が必要です\n", s, t, res) + + // 動的計画法 + res = editDistanceDP(s, t) + fmt.Printf("%s を %s に変更するには最低 %d 回の編集が必要です\n", s, t, res) + + // 空間最適化後の動的計画法 + res = editDistanceDPComp(s, t) + fmt.Printf("%s を %s に変更するには最低 %d 回の編集が必要です\n", s, t, res) +} diff --git a/ja/codes/go/chapter_dynamic_programming/knapsack.go b/ja/codes/go/chapter_dynamic_programming/knapsack.go new file mode 100644 index 000000000..2d4d0d031 --- /dev/null +++ b/ja/codes/go/chapter_dynamic_programming/knapsack.go @@ -0,0 +1,87 @@ +// File: knapsack.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* 0-1 ナップサック:総当たり探索 */ +func knapsackDFS(wgt, val []int, i, c int) int { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if i == 0 || c == 0 { + return 0 + } + // ナップサック容量を超える場合は、入れない選択しかできない + if wgt[i-1] > c { + return knapsackDFS(wgt, val, i-1, c) + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + no := knapsackDFS(wgt, val, i-1, c) + yes := knapsackDFS(wgt, val, i-1, c-wgt[i-1]) + val[i-1] + // 2つの案のうち価値が大きいほうを返す + return int(math.Max(float64(no), float64(yes))) +} + +/* 0-1 ナップサック:メモ化探索 */ +func knapsackDFSMem(wgt, val []int, mem [][]int, i, c int) int { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if i == 0 || c == 0 { + return 0 + } + // 既に記録があればそのまま返す + if mem[i][c] != -1 { + return mem[i][c] + } + // ナップサック容量を超える場合は、入れない選択しかできない + if wgt[i-1] > c { + return knapsackDFSMem(wgt, val, mem, i-1, c) + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + no := knapsackDFSMem(wgt, val, mem, i-1, c) + yes := knapsackDFSMem(wgt, val, mem, i-1, c-wgt[i-1]) + val[i-1] + // 2つの案のうち価値が大きいほうを返す + mem[i][c] = int(math.Max(float64(no), float64(yes))) + return mem[i][c] +} + +/* 0-1 ナップサック:動的計画法 */ +func knapsackDP(wgt, val []int, cap int) int { + n := len(wgt) + // dp テーブルを初期化 + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, cap+1) + } + // 状態遷移 + for i := 1; i <= n; i++ { + for c := 1; c <= cap; c++ { + if wgt[i-1] > c { + // ナップサック容量を超えるなら品物 i は選ばない + dp[i][c] = dp[i-1][c] + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i-1][c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[n][cap] +} + +/* 0-1 ナップサック:空間最適化後の動的計画法 */ +func knapsackDPComp(wgt, val []int, cap int) int { + n := len(wgt) + // dp テーブルを初期化 + dp := make([]int, cap+1) + // 状態遷移 + for i := 1; i <= n; i++ { + // 逆順に走査する + for c := cap; c >= 1; c-- { + if wgt[i-1] <= c { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[cap] +} diff --git a/ja/codes/go/chapter_dynamic_programming/knapsack_test.go b/ja/codes/go/chapter_dynamic_programming/knapsack_test.go new file mode 100644 index 000000000..5383b958d --- /dev/null +++ b/ja/codes/go/chapter_dynamic_programming/knapsack_test.go @@ -0,0 +1,54 @@ +// File: knapsack_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestKnapsack(t *testing.T) { + wgt := []int{10, 20, 30, 40, 50} + val := []int{50, 120, 150, 210, 240} + c := 50 + n := len(wgt) + + // 全探索 + res := knapsackDFS(wgt, val, n, c) + fmt.Printf("ナップサック容量を超えない最大価値は %d\n", res) + + // メモ化探索 + mem := make([][]int, n+1) + for i := 0; i <= n; i++ { + mem[i] = make([]int, c+1) + for j := 0; j <= c; j++ { + mem[i][j] = -1 + } + } + res = knapsackDFSMem(wgt, val, mem, n, c) + fmt.Printf("ナップサック容量を超えない最大価値は %d\n", res) + + // 動的計画法 + res = knapsackDP(wgt, val, c) + fmt.Printf("ナップサック容量を超えない最大価値は %d\n", res) + + // 空間最適化後の動的計画法 + res = knapsackDPComp(wgt, val, c) + fmt.Printf("ナップサック容量を超えない最大価値は %d\n", res) +} + +func TestUnboundedKnapsack(t *testing.T) { + wgt := []int{1, 2, 3} + val := []int{5, 11, 15} + c := 4 + + // 動的計画法 + res := unboundedKnapsackDP(wgt, val, c) + fmt.Printf("ナップサック容量を超えない最大価値は %d\n", res) + + // 空間最適化後の動的計画法 + res = unboundedKnapsackDPComp(wgt, val, c) + fmt.Printf("ナップサック容量を超えない最大価値は %d\n", res) +} diff --git a/ja/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go b/ja/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go new file mode 100644 index 000000000..4eda004de --- /dev/null +++ b/ja/codes/go/chapter_dynamic_programming/min_cost_climbing_stairs_dp.go @@ -0,0 +1,52 @@ +// File: min_cost_climbing_stairs_dp.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +/* 階段登りの最小コスト:動的計画法 */ +func minCostClimbingStairsDP(cost []int) int { + n := len(cost) - 1 + if n == 1 || n == 2 { + return cost[n] + } + min := func(a, b int) int { + if a < b { + return a + } + return b + } + // 部分問題の解を保存するために dp テーブルを初期化 + dp := make([]int, n+1) + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1] = cost[1] + dp[2] = cost[2] + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for i := 3; i <= n; i++ { + dp[i] = min(dp[i-1], dp[i-2]) + cost[i] + } + return dp[n] +} + +/* 階段昇りの最小コスト:空間最適化後の動的計画法 */ +func minCostClimbingStairsDPComp(cost []int) int { + n := len(cost) - 1 + if n == 1 || n == 2 { + return cost[n] + } + min := func(a, b int) int { + if a < b { + return a + } + return b + } + // 初期状態:最小部分問題の解をあらかじめ設定 + a, b := cost[1], cost[2] + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for i := 3; i <= n; i++ { + tmp := b + b = min(a, tmp) + cost[i] + a = tmp + } + return b +} diff --git a/ja/codes/go/chapter_dynamic_programming/min_path_sum.go b/ja/codes/go/chapter_dynamic_programming/min_path_sum.go new file mode 100644 index 000000000..6d8349a47 --- /dev/null +++ b/ja/codes/go/chapter_dynamic_programming/min_path_sum.go @@ -0,0 +1,94 @@ +// File: min_path_sum.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* 最小経路和:全探索 */ +func minPathSumDFS(grid [][]int, i, j int) int { + // 左上のセルなら探索を終了する + if i == 0 && j == 0 { + return grid[0][0] + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if i < 0 || j < 0 { + return math.MaxInt + } + // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する + up := minPathSumDFS(grid, i-1, j) + left := minPathSumDFS(grid, i, j-1) + // 左上隅から (i, j) までの最小経路コストを返す + return int(math.Min(float64(left), float64(up))) + grid[i][j] +} + +/* 最小経路和:メモ化探索 */ +func minPathSumDFSMem(grid, mem [][]int, i, j int) int { + // 左上のセルなら探索を終了する + if i == 0 && j == 0 { + return grid[0][0] + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if i < 0 || j < 0 { + return math.MaxInt + } + // 既に記録があればそのまま返す + if mem[i][j] != -1 { + return mem[i][j] + } + // 左と上のセルからの最小経路コスト + up := minPathSumDFSMem(grid, mem, i-1, j) + left := minPathSumDFSMem(grid, mem, i, j-1) + // 左上から (i, j) までの最小経路コストを記録して返す + mem[i][j] = int(math.Min(float64(left), float64(up))) + grid[i][j] + return mem[i][j] +} + +/* 最小経路和:動的計画法 */ +func minPathSumDP(grid [][]int) int { + n, m := len(grid), len(grid[0]) + // dp テーブルを初期化 + dp := make([][]int, n) + for i := 0; i < n; i++ { + dp[i] = make([]int, m) + } + dp[0][0] = grid[0][0] + // 状態遷移:先頭行 + for j := 1; j < m; j++ { + dp[0][j] = dp[0][j-1] + grid[0][j] + } + // 状態遷移:先頭列 + for i := 1; i < n; i++ { + dp[i][0] = dp[i-1][0] + grid[i][0] + } + // 状態遷移: 残りの行と列 + for i := 1; i < n; i++ { + for j := 1; j < m; j++ { + dp[i][j] = int(math.Min(float64(dp[i][j-1]), float64(dp[i-1][j]))) + grid[i][j] + } + } + return dp[n-1][m-1] +} + +/* 最小経路和:空間最適化後の動的計画法 */ +func minPathSumDPComp(grid [][]int) int { + n, m := len(grid), len(grid[0]) + // dp テーブルを初期化 + dp := make([]int, m) + // 状態遷移:先頭行 + dp[0] = grid[0][0] + for j := 1; j < m; j++ { + dp[j] = dp[j-1] + grid[0][j] + } + // 状態遷移: 残りの行と列 + for i := 1; i < n; i++ { + // 状態遷移:先頭列 + dp[0] = dp[0] + grid[i][0] + // 状態遷移:残りの列 + for j := 1; j < m; j++ { + dp[j] = int(math.Min(float64(dp[j-1]), float64(dp[j]))) + grid[i][j] + } + } + return dp[m-1] +} diff --git a/ja/codes/go/chapter_dynamic_programming/min_path_sum_test.go b/ja/codes/go/chapter_dynamic_programming/min_path_sum_test.go new file mode 100644 index 000000000..1427b5bd1 --- /dev/null +++ b/ja/codes/go/chapter_dynamic_programming/min_path_sum_test.go @@ -0,0 +1,43 @@ +// File: min_path_sum_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import ( + "fmt" + "testing" +) + +func TestMinPathSum(t *testing.T) { + grid := [][]int{ + {1, 3, 1, 5}, + {2, 2, 4, 2}, + {5, 3, 2, 1}, + {4, 3, 5, 2}, + } + n, m := len(grid), len(grid[0]) + + // 全探索 + res := minPathSumDFS(grid, n-1, m-1) + fmt.Printf("左上から右下までの最小経路和は %d\n", res) + + // メモ化探索 + mem := make([][]int, n) + for i := 0; i < n; i++ { + mem[i] = make([]int, m) + for j := 0; j < m; j++ { + mem[i][j] = -1 + } + } + res = minPathSumDFSMem(grid, mem, n-1, m-1) + fmt.Printf("左上から右下までの最小経路和は %d\n", res) + + // 動的計画法 + res = minPathSumDP(grid) + fmt.Printf("左上から右下までの最小経路和は %d\n", res) + + // 空間最適化後の動的計画法 + res = minPathSumDPComp(grid) + fmt.Printf("左上から右下までの最小経路和は %d\n", res) +} diff --git a/ja/codes/go/chapter_dynamic_programming/unbounded_knapsack.go b/ja/codes/go/chapter_dynamic_programming/unbounded_knapsack.go new file mode 100644 index 000000000..585be6b55 --- /dev/null +++ b/ja/codes/go/chapter_dynamic_programming/unbounded_knapsack.go @@ -0,0 +1,50 @@ +// File: unbounded_knapsack.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_dynamic_programming + +import "math" + +/* 完全ナップサック問題:動的計画法 */ +func unboundedKnapsackDP(wgt, val []int, cap int) int { + n := len(wgt) + // dp テーブルを初期化 + dp := make([][]int, n+1) + for i := 0; i <= n; i++ { + dp[i] = make([]int, cap+1) + } + // 状態遷移 + for i := 1; i <= n; i++ { + for c := 1; c <= cap; c++ { + if wgt[i-1] > c { + // ナップサック容量を超えるなら品物 i は選ばない + dp[i][c] = dp[i-1][c] + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i][c] = int(math.Max(float64(dp[i-1][c]), float64(dp[i][c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[n][cap] +} + +/* 完全ナップサック問題:空間最適化後の動的計画法 */ +func unboundedKnapsackDPComp(wgt, val []int, cap int) int { + n := len(wgt) + // dp テーブルを初期化 + dp := make([]int, cap+1) + // 状態遷移 + for i := 1; i <= n; i++ { + for c := 1; c <= cap; c++ { + if wgt[i-1] > c { + // ナップサック容量を超えるなら品物 i は選ばない + dp[c] = dp[c] + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = int(math.Max(float64(dp[c]), float64(dp[c-wgt[i-1]]+val[i-1]))) + } + } + } + return dp[cap] +} diff --git a/ja/codes/go/chapter_graph/graph_adjacency_list.go b/ja/codes/go/chapter_graph/graph_adjacency_list.go new file mode 100644 index 000000000..9268cb355 --- /dev/null +++ b/ja/codes/go/chapter_graph/graph_adjacency_list.go @@ -0,0 +1,100 @@ +// File: graph_adjacency_list.go +// Created Time: 2023-01-31 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "strconv" + "strings" + + . "github.com/krahets/hello-algo/pkg" +) + +/* 隣接リストに基づく無向グラフクラス */ +type graphAdjList struct { + // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 + adjList map[Vertex][]Vertex +} + +/* コンストラクタ */ +func newGraphAdjList(edges [][]Vertex) *graphAdjList { + g := &graphAdjList{ + adjList: make(map[Vertex][]Vertex), + } + // すべての頂点と辺を追加 + for _, edge := range edges { + g.addVertex(edge[0]) + g.addVertex(edge[1]) + g.addEdge(edge[0], edge[1]) + } + return g +} + +/* 頂点数を取得 */ +func (g *graphAdjList) size() int { + return len(g.adjList) +} + +/* 辺を追加 */ +func (g *graphAdjList) addEdge(vet1 Vertex, vet2 Vertex) { + _, ok1 := g.adjList[vet1] + _, ok2 := g.adjList[vet2] + if !ok1 || !ok2 || vet1 == vet2 { + panic("error") + } + // 辺 `vet1 - vet2` を追加し、無名 `struct{}` を追加する + g.adjList[vet1] = append(g.adjList[vet1], vet2) + g.adjList[vet2] = append(g.adjList[vet2], vet1) +} + +/* 辺を削除 */ +func (g *graphAdjList) removeEdge(vet1 Vertex, vet2 Vertex) { + _, ok1 := g.adjList[vet1] + _, ok2 := g.adjList[vet2] + if !ok1 || !ok2 || vet1 == vet2 { + panic("error") + } + // 辺 vet1 - vet2 を削除 + g.adjList[vet1] = DeleteSliceElms(g.adjList[vet1], vet2) + g.adjList[vet2] = DeleteSliceElms(g.adjList[vet2], vet1) +} + +/* 頂点を追加 */ +func (g *graphAdjList) addVertex(vet Vertex) { + _, ok := g.adjList[vet] + if ok { + return + } + // 隣接リストに新しいリストを追加 + g.adjList[vet] = make([]Vertex, 0) +} + +/* 頂点を削除 */ +func (g *graphAdjList) removeVertex(vet Vertex) { + _, ok := g.adjList[vet] + if !ok { + panic("error") + } + // 隣接リストから頂点 vet に対応するリストを削除 + delete(g.adjList, vet) + // 他の頂点のリストを走査し、vet を含むすべての辺を削除 + for v, list := range g.adjList { + g.adjList[v] = DeleteSliceElms(list, vet) + } +} + +/* 隣接リストを出力 */ +func (g *graphAdjList) print() { + var builder strings.Builder + fmt.Printf("隣接リスト = \n") + for k, v := range g.adjList { + builder.WriteString("\t\t" + strconv.Itoa(k.Val) + ": ") + for _, vet := range v { + builder.WriteString(strconv.Itoa(vet.Val) + " ") + } + fmt.Println(builder.String()) + builder.Reset() + } +} diff --git a/ja/codes/go/chapter_graph/graph_adjacency_list_test.go b/ja/codes/go/chapter_graph/graph_adjacency_list_test.go new file mode 100644 index 000000000..e0874a7f7 --- /dev/null +++ b/ja/codes/go/chapter_graph/graph_adjacency_list_test.go @@ -0,0 +1,45 @@ +// File: graph_adjacency_list_test.go +// Created Time: 2023-01-31 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestGraphAdjList(t *testing.T) { + /* 無向グラフを初期化 */ + v := ValsToVets([]int{1, 3, 2, 5, 4}) + edges := [][]Vertex{{v[0], v[1]}, {v[0], v[3]}, {v[1], v[2]}, {v[2], v[3]}, {v[2], v[4]}, {v[3], v[4]}} + graph := newGraphAdjList(edges) + fmt.Println("初期化後、グラフは:") + graph.print() + + /* 辺を追加 */ + // 頂点 1, 2 は v[0], v[2] + graph.addEdge(v[0], v[2]) + fmt.Println("\n辺 1-2 を追加後、グラフは") + graph.print() + + /* 辺を削除 */ + // 頂点 1, 3 は v[0], v[1] + graph.removeEdge(v[0], v[1]) + fmt.Println("\n辺 1-3 を削除後、グラフは") + graph.print() + + /* 頂点を追加 */ + v5 := NewVertex(6) + graph.addVertex(v5) + fmt.Println("\n頂点 6 を追加後、グラフは") + graph.print() + + /* 頂点を削除 */ + // 頂点 3 は v[1] + graph.removeVertex(v[1]) + fmt.Println("\n頂点 3 を削除後、グラフは") + graph.print() +} diff --git a/ja/codes/go/chapter_graph/graph_adjacency_matrix.go b/ja/codes/go/chapter_graph/graph_adjacency_matrix.go new file mode 100644 index 000000000..8743f0fc6 --- /dev/null +++ b/ja/codes/go/chapter_graph/graph_adjacency_matrix.go @@ -0,0 +1,102 @@ +// File: graph_adjacency_matrix.go +// Created Time: 2023-01-31 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import "fmt" + +/* 隣接行列に基づく無向グラフクラス */ +type graphAdjMat struct { + // 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す + vertices []int + // 隣接行列。行・列のインデックスは「頂点インデックス」に対応 + adjMat [][]int +} + +/* コンストラクタ */ +func newGraphAdjMat(vertices []int, edges [][]int) *graphAdjMat { + // 頂点を追加 + n := len(vertices) + adjMat := make([][]int, n) + for i := range adjMat { + adjMat[i] = make([]int, n) + } + // グラフを初期化する + g := &graphAdjMat{ + vertices: vertices, + adjMat: adjMat, + } + // 辺を追加 + // 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する + for i := range edges { + g.addEdge(edges[i][0], edges[i][1]) + } + return g +} + +/* 頂点数を取得 */ +func (g *graphAdjMat) size() int { + return len(g.vertices) +} + +/* 頂点を追加 */ +func (g *graphAdjMat) addVertex(val int) { + n := g.size() + // 頂点リストに新しい頂点の値を追加 + g.vertices = append(g.vertices, val) + // 隣接行列に 1 行追加 + newRow := make([]int, n) + g.adjMat = append(g.adjMat, newRow) + // 隣接行列に 1 列追加 + for i := range g.adjMat { + g.adjMat[i] = append(g.adjMat[i], 0) + } +} + +/* 頂点を削除 */ +func (g *graphAdjMat) removeVertex(index int) { + if index >= g.size() { + return + } + // 頂点リストから index の頂点を削除する + g.vertices = append(g.vertices[:index], g.vertices[index+1:]...) + // 隣接行列で index 行を削除する + g.adjMat = append(g.adjMat[:index], g.adjMat[index+1:]...) + // 隣接行列で index 列を削除する + for i := range g.adjMat { + g.adjMat[i] = append(g.adjMat[i][:index], g.adjMat[i][index+1:]...) + } +} + +/* 辺を追加 */ +// 引数 i, j は vertices の要素インデックスに対応する +func (g *graphAdjMat) addEdge(i, j int) { + // インデックスの範囲外と等値の処理 + if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { + fmt.Errorf("%s", "Index Out Of Bounds Exception") + } + // 無向グラフでは、隣接行列は主対角線に関して対称、すなわち (i, j) == (j, i) を満たす + g.adjMat[i][j] = 1 + g.adjMat[j][i] = 1 +} + +/* 辺を削除 */ +// 引数 i, j は vertices の要素インデックスに対応する +func (g *graphAdjMat) removeEdge(i, j int) { + // インデックスの範囲外と等値の処理 + if i < 0 || j < 0 || i >= g.size() || j >= g.size() || i == j { + fmt.Errorf("%s", "Index Out Of Bounds Exception") + } + g.adjMat[i][j] = 0 + g.adjMat[j][i] = 0 +} + +/* 隣接行列を出力 */ +func (g *graphAdjMat) print() { + fmt.Printf("\t頂点リスト = %v\n", g.vertices) + fmt.Printf("\t隣接行列 = \n") + for i := range g.adjMat { + fmt.Printf("\t\t\t%v\n", g.adjMat[i]) + } +} diff --git a/ja/codes/go/chapter_graph/graph_adjacency_matrix_test.go b/ja/codes/go/chapter_graph/graph_adjacency_matrix_test.go new file mode 100644 index 000000000..b2b83dd6c --- /dev/null +++ b/ja/codes/go/chapter_graph/graph_adjacency_matrix_test.go @@ -0,0 +1,43 @@ +// File: graph_adjacency_matrix_test.go +// Created Time: 2023-01-31 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "testing" +) + +func TestGraphAdjMat(t *testing.T) { + /* 無向グラフを初期化 */ + // edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意 + vertices := []int{1, 3, 2, 5, 4} + edges := [][]int{{0, 1}, {1, 2}, {2, 3}, {0, 3}, {2, 4}, {3, 4}} + graph := newGraphAdjMat(vertices, edges) + fmt.Println("初期化後、グラフは:") + graph.print() + + /* 辺を追加 */ + // 頂点 1, 2 のインデックスはそれぞれ 0, 2 + graph.addEdge(0, 2) + fmt.Println("辺 1-2 を追加後、グラフは") + graph.print() + + /* 辺を削除 */ + // 頂点 1, 3 のインデックスはそれぞれ 0, 1 + graph.removeEdge(0, 1) + fmt.Println("辺 1-3 を削除後、グラフは") + graph.print() + + /* 頂点を追加 */ + graph.addVertex(6) + fmt.Println("頂点 6 を追加後、グラフは") + graph.print() + + /* 頂点を削除 */ + // 頂点 3 のインデックスは 1 + graph.removeVertex(1) + fmt.Println("頂点 3 を削除後、グラフは") + graph.print() +} diff --git a/ja/codes/go/chapter_graph/graph_bfs.go b/ja/codes/go/chapter_graph/graph_bfs.go new file mode 100644 index 000000000..c62908a68 --- /dev/null +++ b/ja/codes/go/chapter_graph/graph_bfs.go @@ -0,0 +1,41 @@ +// File: graph_bfs.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 幅優先探索 */ +// グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする +func graphBFS(g *graphAdjList, startVet Vertex) []Vertex { + // 頂点の走査順序 + res := make([]Vertex, 0) + // 訪問済み頂点を記録するためのハッシュ集合 + visited := make(map[Vertex]struct{}) + visited[startVet] = struct{}{} + // キューは BFS の実装に用い、スライスでキューをシミュレートする + queue := make([]Vertex, 0) + queue = append(queue, startVet) + // 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す + for len(queue) > 0 { + // 先頭の頂点をデキュー + vet := queue[0] + queue = queue[1:] + // 訪問した頂点を記録 + res = append(res, vet) + // この頂点のすべての隣接頂点を走査 + for _, adjVet := range g.adjList[vet] { + _, isExist := visited[adjVet] + // 未訪問の頂点のみをキューに追加 + if !isExist { + queue = append(queue, adjVet) + visited[adjVet] = struct{}{} + } + } + } + // 頂点の走査順を返す + return res +} diff --git a/ja/codes/go/chapter_graph/graph_bfs_test.go b/ja/codes/go/chapter_graph/graph_bfs_test.go new file mode 100644 index 000000000..59a102e19 --- /dev/null +++ b/ja/codes/go/chapter_graph/graph_bfs_test.go @@ -0,0 +1,29 @@ +// File: graph_bfs_test.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestGraphBFS(t *testing.T) { + /* 無向グラフを初期化 */ + vets := ValsToVets([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) + edges := [][]Vertex{ + {vets[0], vets[1]}, {vets[0], vets[3]}, {vets[1], vets[2]}, {vets[1], vets[4]}, + {vets[2], vets[5]}, {vets[3], vets[4]}, {vets[3], vets[6]}, {vets[4], vets[5]}, + {vets[4], vets[7]}, {vets[5], vets[8]}, {vets[6], vets[7]}, {vets[7], vets[8]}} + graph := newGraphAdjList(edges) + fmt.Println("初期化後、グラフは:") + graph.print() + + /* 幅優先探索 */ + res := graphBFS(graph, vets[0]) + fmt.Println("幅優先探索(BFS)の頂点列:") + PrintSlice(VetsToVals(res)) +} diff --git a/ja/codes/go/chapter_graph/graph_dfs.go b/ja/codes/go/chapter_graph/graph_dfs.go new file mode 100644 index 000000000..fe3d0a85c --- /dev/null +++ b/ja/codes/go/chapter_graph/graph_dfs.go @@ -0,0 +1,36 @@ +// File: graph_dfs.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 深さ優先走査の補助関数 */ +func dfs(g *graphAdjList, visited map[Vertex]struct{}, res *[]Vertex, vet Vertex) { + // append 操作は新しい参照を返すため、元の参照を新しい slice の参照で再代入する必要がある + *res = append(*res, vet) + visited[vet] = struct{}{} + // この頂点のすべての隣接頂点を走査 + for _, adjVet := range g.adjList[vet] { + _, isExist := visited[adjVet] + // 隣接頂点を再帰的に訪問 + if !isExist { + dfs(g, visited, res, adjVet) + } + } +} + +/* 深さ優先探索 */ +// グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする +func graphDFS(g *graphAdjList, startVet Vertex) []Vertex { + // 頂点の走査順序 + res := make([]Vertex, 0) + // 訪問済み頂点を記録するためのハッシュ集合 + visited := make(map[Vertex]struct{}) + dfs(g, visited, &res, startVet) + // 頂点の走査順を返す + return res +} diff --git a/ja/codes/go/chapter_graph/graph_dfs_test.go b/ja/codes/go/chapter_graph/graph_dfs_test.go new file mode 100644 index 000000000..0f4058983 --- /dev/null +++ b/ja/codes/go/chapter_graph/graph_dfs_test.go @@ -0,0 +1,28 @@ +// File: graph_dfs_test.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package chapter_graph + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestGraphDFS(t *testing.T) { + /* 無向グラフを初期化 */ + vets := ValsToVets([]int{0, 1, 2, 3, 4, 5, 6}) + edges := [][]Vertex{ + {vets[0], vets[1]}, {vets[0], vets[3]}, {vets[1], vets[2]}, + {vets[2], vets[5]}, {vets[4], vets[5]}, {vets[5], vets[6]}} + graph := newGraphAdjList(edges) + fmt.Println("初期化後、グラフは:") + graph.print() + + /* 深さ優先探索 */ + res := graphDFS(graph, vets[0]) + fmt.Println("深さ優先探索(DFS)の頂点列:") + PrintSlice(VetsToVals(res)) +} diff --git a/ja/codes/go/chapter_greedy/coin_change_greedy.go b/ja/codes/go/chapter_greedy/coin_change_greedy.go new file mode 100644 index 000000000..27a55fbe6 --- /dev/null +++ b/ja/codes/go/chapter_greedy/coin_change_greedy.go @@ -0,0 +1,27 @@ +// File: coin_change_greedy.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +/* コイン交換:貪欲法 */ +func coinChangeGreedy(coins []int, amt int) int { + // coins リストはソート済みと仮定する + i := len(coins) - 1 + count := 0 + // 残額がなくなるまで貪欲選択を繰り返す + for amt > 0 { + // 残額以下で最も近い硬貨を見つける + for i > 0 && coins[i] > amt { + i-- + } + // coins[i] を選択する + amt -= coins[i] + count++ + } + // 実行可能な解が見つからなければ -1 を返す + if amt != 0 { + return -1 + } + return count +} diff --git a/ja/codes/go/chapter_greedy/coin_change_greedy_test.go b/ja/codes/go/chapter_greedy/coin_change_greedy_test.go new file mode 100644 index 000000000..76851ed76 --- /dev/null +++ b/ja/codes/go/chapter_greedy/coin_change_greedy_test.go @@ -0,0 +1,35 @@ +// File: coin_change_greedy_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import ( + "fmt" + "testing" +) + +func TestCoinChangeGreedy(t *testing.T) { + // 貪欲法:大域最適解を保証できる + coins := []int{1, 5, 10, 20, 50, 100} + amt := 186 + res := coinChangeGreedy(coins, amt) + fmt.Printf("coins = %v, amt = %d\n", coins, amt) + fmt.Printf("%d を作るのに必要な最小硬貨枚数は %d\n", amt, res) + + // 貪欲法:大域最適解を保証できない + coins = []int{1, 20, 50} + amt = 60 + res = coinChangeGreedy(coins, amt) + fmt.Printf("coins = %v, amt = %d\n", coins, amt) + fmt.Printf("%d を作るのに必要な最小硬貨枚数は %d\n", amt, res) + fmt.Println("実際に必要な最小枚数は 3、つまり 20 + 20 + 20") + + // 貪欲法:大域最適解を保証できない + coins = []int{1, 49, 50} + amt = 98 + res = coinChangeGreedy(coins, amt) + fmt.Printf("coins = %v, amt = %d\n", coins, amt) + fmt.Printf("%d を作るのに必要な最小硬貨枚数は %d\n", amt, res) + fmt.Println("実際に必要な最小枚数は 2、つまり 49 + 49") +} diff --git a/ja/codes/go/chapter_greedy/fractional_knapsack.go b/ja/codes/go/chapter_greedy/fractional_knapsack.go new file mode 100644 index 000000000..bba9af366 --- /dev/null +++ b/ja/codes/go/chapter_greedy/fractional_knapsack.go @@ -0,0 +1,41 @@ +// File: fractional_knapsack.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import "sort" + +/* 品物 */ +type Item struct { + w int // 品物の重さ + v int // 品物の価値 +} + +/* 分数ナップサック:貪欲法 */ +func fractionalKnapsack(wgt []int, val []int, cap int) float64 { + // 重さと価値の 2 属性を持つ品物リストを作成 + items := make([]Item, len(wgt)) + for i := 0; i < len(wgt); i++ { + items[i] = Item{wgt[i], val[i]} + } + // 単位価値 item.v / item.w の高い順にソートする + sort.Slice(items, func(i, j int) bool { + return float64(items[i].v)/float64(items[i].w) > float64(items[j].v)/float64(items[j].w) + }) + // 貪欲選択を繰り返す + res := 0.0 + for _, item := range items { + if item.w <= cap { + // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる + res += float64(item.v) + cap -= item.w + } else { + // 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる + res += float64(item.v) / float64(item.w) * float64(cap) + // 残り容量がないため、ループを抜ける + break + } + } + return res +} diff --git a/ja/codes/go/chapter_greedy/fractional_knapsack_test.go b/ja/codes/go/chapter_greedy/fractional_knapsack_test.go new file mode 100644 index 000000000..369e8ab33 --- /dev/null +++ b/ja/codes/go/chapter_greedy/fractional_knapsack_test.go @@ -0,0 +1,20 @@ +// File: fractional_knapsack_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import ( + "fmt" + "testing" +) + +func TestFractionalKnapsack(t *testing.T) { + wgt := []int{10, 20, 30, 40, 50} + val := []int{50, 120, 150, 210, 240} + capacity := 50 + + // 貪欲法 + res := fractionalKnapsack(wgt, val, capacity) + fmt.Println("ナップサック容量を超えない最大価値は", res) +} diff --git a/ja/codes/go/chapter_greedy/max_capacity.go b/ja/codes/go/chapter_greedy/max_capacity.go new file mode 100644 index 000000000..d42f191ef --- /dev/null +++ b/ja/codes/go/chapter_greedy/max_capacity.go @@ -0,0 +1,28 @@ +// File: max_capacity.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import "math" + +/* 最大容量:貪欲法 */ +func maxCapacity(ht []int) int { + // i, j を初期化し、それぞれ配列の両端に置く + i, j := 0, len(ht)-1 + // 初期の最大容量は 0 + res := 0 + // 2 枚の板が出会うまで貪欲選択を繰り返す + for i < j { + // 最大容量を更新する + capacity := int(math.Min(float64(ht[i]), float64(ht[j]))) * (j - i) + res = int(math.Max(float64(res), float64(capacity))) + // 短い方を内側へ動かす + if ht[i] < ht[j] { + i++ + } else { + j-- + } + } + return res +} diff --git a/ja/codes/go/chapter_greedy/max_capacity_test.go b/ja/codes/go/chapter_greedy/max_capacity_test.go new file mode 100644 index 000000000..6fc52b13b --- /dev/null +++ b/ja/codes/go/chapter_greedy/max_capacity_test.go @@ -0,0 +1,18 @@ +// File: max_capacity_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import ( + "fmt" + "testing" +) + +func TestMaxCapacity(t *testing.T) { + ht := []int{3, 8, 5, 2, 7, 7, 3, 4} + + // 貪欲法 + res := maxCapacity(ht) + fmt.Println("最大容量は", res) +} diff --git a/ja/codes/go/chapter_greedy/max_product_cutting.go b/ja/codes/go/chapter_greedy/max_product_cutting.go new file mode 100644 index 000000000..fcfe63f5c --- /dev/null +++ b/ja/codes/go/chapter_greedy/max_product_cutting.go @@ -0,0 +1,28 @@ +// File: max_product_cutting.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import "math" + +/* 最大切断積:貪欲法 */ +func maxProductCutting(n int) int { + // n <= 3 のときは、必ず 1 を切り出す + if n <= 3 { + return 1 * (n - 1) + } + // 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする + a := n / 3 + b := n % 3 + if b == 1 { + // 余りが 1 のときは、1 * 3 を 2 * 2 に変える + return int(math.Pow(3, float64(a-1))) * 2 * 2 + } + if b == 2 { + // 余りが 2 のときは、そのままにする + return int(math.Pow(3, float64(a))) * 2 + } + // 余りが 0 のときは、そのままにする + return int(math.Pow(3, float64(a))) +} diff --git a/ja/codes/go/chapter_greedy/max_product_cutting_test.go b/ja/codes/go/chapter_greedy/max_product_cutting_test.go new file mode 100644 index 000000000..d5e337ca4 --- /dev/null +++ b/ja/codes/go/chapter_greedy/max_product_cutting_test.go @@ -0,0 +1,17 @@ +// File: max_product_cutting_test.go +// Created Time: 2023-07-23 +// Author: Reanon (793584285@qq.com) + +package chapter_greedy + +import ( + "fmt" + "testing" +) + +func TestMaxProductCutting(t *testing.T) { + n := 58 + // 貪欲法 + res := maxProductCutting(n) + fmt.Println("最大分割積は", res) +} diff --git a/ja/codes/go/chapter_hashing/array_hash_map.go b/ja/codes/go/chapter_hashing/array_hash_map.go new file mode 100644 index 000000000..d9492fb5a --- /dev/null +++ b/ja/codes/go/chapter_hashing/array_hash_map.go @@ -0,0 +1,97 @@ +// File: array_hash_map.go +// Created Time: 2022-12-14 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_hashing + +import "fmt" + +/* キーと値の組 */ +type pair struct { + key int + val string +} + +/* 配列ベースのハッシュテーブル */ +type arrayHashMap struct { + buckets []*pair +} + +/* ハッシュテーブルを初期化 */ +func newArrayHashMap() *arrayHashMap { + // 100 個のバケットを含む配列を初期化 + buckets := make([]*pair, 100) + return &arrayHashMap{buckets: buckets} +} + +/* ハッシュ関数 */ +func (a *arrayHashMap) hashFunc(key int) int { + index := key % 100 + return index +} + +/* 検索操作 */ +func (a *arrayHashMap) get(key int) string { + index := a.hashFunc(key) + pair := a.buckets[index] + if pair == nil { + return "Not Found" + } + return pair.val +} + +/* 追加操作 */ +func (a *arrayHashMap) put(key int, val string) { + pair := &pair{key: key, val: val} + index := a.hashFunc(key) + a.buckets[index] = pair +} + +/* 削除操作 */ +func (a *arrayHashMap) remove(key int) { + index := a.hashFunc(key) + // nil に設定し、削除を表す + a.buckets[index] = nil +} + +/* すべてのキーのペアを取得する */ +func (a *arrayHashMap) pairSet() []*pair { + var pairs []*pair + for _, pair := range a.buckets { + if pair != nil { + pairs = append(pairs, pair) + } + } + return pairs +} + +/* すべてのキーを取得 */ +func (a *arrayHashMap) keySet() []int { + var keys []int + for _, pair := range a.buckets { + if pair != nil { + keys = append(keys, pair.key) + } + } + return keys +} + +/* すべての値を取得 */ +func (a *arrayHashMap) valueSet() []string { + var values []string + for _, pair := range a.buckets { + if pair != nil { + values = append(values, pair.val) + } + } + return values +} + +/* ハッシュテーブルを出力 */ +func (a *arrayHashMap) print() { + for _, pair := range a.buckets { + if pair != nil { + fmt.Println(pair.key, "->", pair.val) + } + } +} diff --git a/ja/codes/go/chapter_hashing/array_hash_map_test.go b/ja/codes/go/chapter_hashing/array_hash_map_test.go new file mode 100644 index 000000000..3c0c58c7b --- /dev/null +++ b/ja/codes/go/chapter_hashing/array_hash_map_test.go @@ -0,0 +1,52 @@ +// File: array_hash_map_test.go +// Created Time: 2022-12-14 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_hashing + +import ( + "fmt" + "testing" +) + +func TestArrayHashMap(t *testing.T) { + /* ハッシュテーブルを初期化 */ + hmap := newArrayHashMap() + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + hmap.put(12836, "シャオハー") + hmap.put(15937, "シャオルオ") + hmap.put(16750, "シャオスワン") + hmap.put(13276, "シャオファー") + hmap.put(10583, "シャオヤ") + fmt.Println("\n追加後、ハッシュ表は\nKey -> Value") + hmap.print() + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + name := hmap.get(15937) + fmt.Println("\n学籍番号 15937 を入力し、見つかった名前は " + name) + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + hmap.remove(10583) + fmt.Println("\n10583 を削除後、ハッシュ表は\nKey -> Value") + hmap.print() + + /* ハッシュテーブルを走査 */ + fmt.Println("\nキーと値の組 Key->Value を走査") + for _, kv := range hmap.pairSet() { + fmt.Println(kv.key, " -> ", kv.val) + } + + fmt.Println("\nキー Key を個別に走査") + for _, key := range hmap.keySet() { + fmt.Println(key) + } + + fmt.Println("\n値 Value を個別に走査") + for _, val := range hmap.valueSet() { + fmt.Println(val) + } +} diff --git a/ja/codes/go/chapter_hashing/hash_collision_test.go b/ja/codes/go/chapter_hashing/hash_collision_test.go new file mode 100644 index 000000000..d415855d1 --- /dev/null +++ b/ja/codes/go/chapter_hashing/hash_collision_test.go @@ -0,0 +1,62 @@ +// File: hash_collision_test.go +// Created Time: 2022-12-14 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_hashing + +import ( + "fmt" + "testing" +) + +func TestHashMapChaining(t *testing.T) { + /* ハッシュテーブルを初期化 */ + hmap := newHashMapChaining() + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + hmap.put(12836, "シャオハー") + hmap.put(15937, "シャオルオ") + hmap.put(16750, "シャオスワン") + hmap.put(13276, "シャオファー") + hmap.put(10583, "シャオヤ") + fmt.Println("\n追加後、ハッシュ表は\nKey -> Value") + hmap.print() + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + name := hmap.get(15937) + fmt.Println("\n学籍番号 15937 を入力し、見つかった名前は", name) + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + hmap.remove(12836) + fmt.Println("\n12836 を削除後、ハッシュ表は\nKey -> Value") + hmap.print() +} + +func TestHashMapOpenAddressing(t *testing.T) { + /* ハッシュテーブルを初期化 */ + hmap := newHashMapOpenAddressing() + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + hmap.put(12836, "シャオハー") + hmap.put(15937, "シャオルオ") + hmap.put(16750, "シャオスワン") + hmap.put(13276, "シャオファー") + hmap.put(10583, "シャオヤ") + fmt.Println("\n追加後、ハッシュ表は\nKey -> Value") + hmap.print() + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + name := hmap.get(13276) + fmt.Println("\n学籍番号 13276 を入力し、見つかった名前は ", name) + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + hmap.remove(16750) + fmt.Println("\n16750 を削除後、ハッシュ表は\nKey -> Value") + hmap.print() +} diff --git a/ja/codes/go/chapter_hashing/hash_map_chaining.go b/ja/codes/go/chapter_hashing/hash_map_chaining.go new file mode 100644 index 000000000..f4334977e --- /dev/null +++ b/ja/codes/go/chapter_hashing/hash_map_chaining.go @@ -0,0 +1,134 @@ +// File: hash_map_chaining.go +// Created Time: 2023-06-23 +// Author: Reanon (793584285@qq.com) + +package chapter_hashing + +import ( + "fmt" + "strconv" + "strings" +) + +/* チェイン法ハッシュテーブル */ +type hashMapChaining struct { + size int // キーと値のペア数 + capacity int // ハッシュテーブル容量 + loadThres float64 // リサイズを発動する負荷率のしきい値 + extendRatio int // 拡張倍率 + buckets [][]pair // バケット配列 +} + +/* コンストラクタ */ +func newHashMapChaining() *hashMapChaining { + buckets := make([][]pair, 4) + for i := 0; i < 4; i++ { + buckets[i] = make([]pair, 0) + } + return &hashMapChaining{ + size: 0, + capacity: 4, + loadThres: 2.0 / 3.0, + extendRatio: 2, + buckets: buckets, + } +} + +/* ハッシュ関数 */ +func (m *hashMapChaining) hashFunc(key int) int { + return key % m.capacity +} + +/* 負荷率 */ +func (m *hashMapChaining) loadFactor() float64 { + return float64(m.size) / float64(m.capacity) +} + +/* 検索操作 */ +func (m *hashMapChaining) get(key int) string { + idx := m.hashFunc(key) + bucket := m.buckets[idx] + // バケットを走査し、key が見つかれば対応する val を返す + for _, p := range bucket { + if p.key == key { + return p.val + } + } + // key が見つからない場合は空文字列を返す + return "" +} + +/* 追加操作 */ +func (m *hashMapChaining) put(key int, val string) { + // 負荷率がしきい値を超えたら、リサイズを実行 + if m.loadFactor() > m.loadThres { + m.extend() + } + idx := m.hashFunc(key) + // バケットを走査し、指定した key が見つかれば対応する val を更新して返す + for i := range m.buckets[idx] { + if m.buckets[idx][i].key == key { + m.buckets[idx][i].val = val + return + } + } + // その key が存在しなければ、キーと値のペアを末尾に追加 + p := pair{ + key: key, + val: val, + } + m.buckets[idx] = append(m.buckets[idx], p) + m.size += 1 +} + +/* 削除操作 */ +func (m *hashMapChaining) remove(key int) { + idx := m.hashFunc(key) + // バケットを走査してキーと値のペアを削除 + for i, p := range m.buckets[idx] { + if p.key == key { + // スライスから削除する + m.buckets[idx] = append(m.buckets[idx][:i], m.buckets[idx][i+1:]...) + m.size -= 1 + break + } + } +} + +/* ハッシュテーブルを拡張 */ +func (m *hashMapChaining) extend() { + // 元のハッシュテーブルを一時保存 + tmpBuckets := make([][]pair, len(m.buckets)) + for i := 0; i < len(m.buckets); i++ { + tmpBuckets[i] = make([]pair, len(m.buckets[i])) + copy(tmpBuckets[i], m.buckets[i]) + } + // リサイズ後の新しいハッシュテーブルを初期化 + m.capacity *= m.extendRatio + m.buckets = make([][]pair, m.capacity) + for i := 0; i < m.capacity; i++ { + m.buckets[i] = make([]pair, 0) + } + m.size = 0 + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す + for _, bucket := range tmpBuckets { + for _, p := range bucket { + m.put(p.key, p.val) + } + } +} + +/* ハッシュテーブルを出力 */ +func (m *hashMapChaining) print() { + var builder strings.Builder + + for _, bucket := range m.buckets { + builder.WriteString("[") + for _, p := range bucket { + builder.WriteString(strconv.Itoa(p.key) + " -> " + p.val + " ") + } + builder.WriteString("]") + fmt.Println(builder.String()) + builder.Reset() + } +} diff --git a/ja/codes/go/chapter_hashing/hash_map_open_addressing.go b/ja/codes/go/chapter_hashing/hash_map_open_addressing.go new file mode 100644 index 000000000..c825a2ee8 --- /dev/null +++ b/ja/codes/go/chapter_hashing/hash_map_open_addressing.go @@ -0,0 +1,126 @@ +// File: hash_map_open_addressing.go +// Created Time: 2023-06-23 +// Author: Reanon (793584285@qq.com) + +package chapter_hashing + +import ( + "fmt" +) + +/* オープンアドレス法ハッシュテーブル */ +type hashMapOpenAddressing struct { + size int // キーと値のペア数 + capacity int // ハッシュテーブル容量 + loadThres float64 // リサイズを発動する負荷率のしきい値 + extendRatio int // 拡張倍率 + buckets []*pair // バケット配列 + TOMBSTONE *pair // 削除済みマーク +} + +/* コンストラクタ */ +func newHashMapOpenAddressing() *hashMapOpenAddressing { + return &hashMapOpenAddressing{ + size: 0, + capacity: 4, + loadThres: 2.0 / 3.0, + extendRatio: 2, + buckets: make([]*pair, 4), + TOMBSTONE: &pair{-1, "-1"}, + } +} + +/* ハッシュ関数 */ +func (h *hashMapOpenAddressing) hashFunc(key int) int { + return key % h.capacity // キーに基づいてハッシュ値を計算 +} + +/* 負荷率 */ +func (h *hashMapOpenAddressing) loadFactor() float64 { + return float64(h.size) / float64(h.capacity) // 現在の負荷率を計算 +} + +/* key に対応するバケットインデックスを探す */ +func (h *hashMapOpenAddressing) findBucket(key int) int { + index := h.hashFunc(key) // 初期インデックスを取得 + firstTombstone := -1 // 最初に遭遇した `TOMBSTONE` の位置を記録する + for h.buckets[index] != nil { + if h.buckets[index].key == key { + if firstTombstone != -1 { + // 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 + h.buckets[firstTombstone] = h.buckets[index] + h.buckets[index] = h.TOMBSTONE + return firstTombstone // 移動後のバケットインデックスを返す + } + return index // 見つかったインデックスを返す + } + if firstTombstone == -1 && h.buckets[index] == h.TOMBSTONE { + firstTombstone = index // 最初に遭遇した削除マークの位置を記録する + } + index = (index + 1) % h.capacity // 線形探索を行い、末尾を越えたら先頭に戻る + } + // key が存在しない場合は追加位置のインデックスを返す + if firstTombstone != -1 { + return firstTombstone + } + return index +} + +/* 検索操作 */ +func (h *hashMapOpenAddressing) get(key int) string { + index := h.findBucket(key) // key に対応するバケットインデックスを探す + if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { + return h.buckets[index].val // キーと値の組が見つかったら、対応する val を返す + } + return "" // キーと値のペアが存在しない場合は `""` を返す +} + +/* 追加操作 */ +func (h *hashMapOpenAddressing) put(key int, val string) { + if h.loadFactor() > h.loadThres { + h.extend() // 負荷率がしきい値を超えたら、リサイズを実行 + } + index := h.findBucket(key) // key に対応するバケットインデックスを探す + if h.buckets[index] == nil || h.buckets[index] == h.TOMBSTONE { + h.buckets[index] = &pair{key, val} // キーと値の組が存在しない場合は、その組を追加する + h.size++ + } else { + h.buckets[index].val = val // キーと値のペアが見つかった場合は、`val` を上書きする + } +} + +/* 削除操作 */ +func (h *hashMapOpenAddressing) remove(key int) { + index := h.findBucket(key) // key に対応するバケットインデックスを探す + if h.buckets[index] != nil && h.buckets[index] != h.TOMBSTONE { + h.buckets[index] = h.TOMBSTONE // キーと値の組が見つかったら、削除マーカーで上書きする + h.size-- + } +} + +/* ハッシュテーブルを拡張 */ +func (h *hashMapOpenAddressing) extend() { + oldBuckets := h.buckets // 元のハッシュテーブルを一時保存 + h.capacity *= h.extendRatio // 容量を更新 + h.buckets = make([]*pair, h.capacity) // リサイズ後の新しいハッシュテーブルを初期化 + h.size = 0 // サイズをリセットする + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す + for _, pair := range oldBuckets { + if pair != nil && pair != h.TOMBSTONE { + h.put(pair.key, pair.val) + } + } +} + +/* ハッシュテーブルを出力 */ +func (h *hashMapOpenAddressing) print() { + for _, pair := range h.buckets { + if pair == nil { + fmt.Println("nil") + } else if pair == h.TOMBSTONE { + fmt.Println("TOMBSTONE") + } else { + fmt.Printf("%d -> %s\n", pair.key, pair.val) + } + } +} diff --git a/ja/codes/go/chapter_hashing/hash_map_test.go b/ja/codes/go/chapter_hashing/hash_map_test.go new file mode 100644 index 000000000..026399c2d --- /dev/null +++ b/ja/codes/go/chapter_hashing/hash_map_test.go @@ -0,0 +1,74 @@ +// File: hash_map_test.go +// Created Time: 2022-12-14 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_hashing + +import ( + "fmt" + "strconv" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestHashMap(t *testing.T) { + /* ハッシュテーブルを初期化 */ + hmap := make(map[int]string) + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + hmap[12836] = "シャオハー" + hmap[15937] = "シャオルオ" + hmap[16750] = "シャオスワン" + hmap[13276] = "シャオファー" + hmap[10583] = "シャオヤ" + fmt.Println("\n追加後、ハッシュ表は\nKey -> Value") + PrintMap(hmap) + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + name := hmap[15937] + fmt.Println("\n学籍番号 15937 を入力し、見つかった名前は ", name) + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + delete(hmap, 10583) + fmt.Println("\n10583 を削除後、ハッシュ表は\nKey -> Value") + PrintMap(hmap) + + /* ハッシュテーブルを走査 */ + // キーと値の組 key->value を走査する + fmt.Println("\nキーと値の組 Key->Value を走査") + for key, value := range hmap { + fmt.Println(key, "->", value) + } + // キー key のみを走査する + fmt.Println("\nキー Key を個別に走査") + for key := range hmap { + fmt.Println(key) + } + // 値 value のみを走査する + fmt.Println("\n値 Value を個別に走査") + for _, value := range hmap { + fmt.Println(value) + } +} + +func TestSimpleHash(t *testing.T) { + var hash int + + key := "Hello アルゴリズム" + + hash = addHash(key) + fmt.Println("加算ハッシュ値は " + strconv.Itoa(hash)) + + hash = mulHash(key) + fmt.Println("乗算ハッシュ値は " + strconv.Itoa(hash)) + + hash = xorHash(key) + fmt.Println("排他的論理和ハッシュ値は " + strconv.Itoa(hash)) + + hash = rotHash(key) + fmt.Println("回転ハッシュ値は " + strconv.Itoa(hash)) +} diff --git a/ja/codes/go/chapter_hashing/simple_hash.go b/ja/codes/go/chapter_hashing/simple_hash.go new file mode 100644 index 000000000..9dec84787 --- /dev/null +++ b/ja/codes/go/chapter_hashing/simple_hash.go @@ -0,0 +1,55 @@ +// File: simple_hash.go +// Created Time: 2023-06-23 +// Author: Reanon (793584285@qq.com) + +package chapter_hashing + +import "fmt" + +/* 加算ハッシュ */ +func addHash(key string) int { + var hash int64 + var modulus int64 + + modulus = 1000000007 + for _, b := range []byte(key) { + hash = (hash + int64(b)) % modulus + } + return int(hash) +} + +/* 乗算ハッシュ */ +func mulHash(key string) int { + var hash int64 + var modulus int64 + + modulus = 1000000007 + for _, b := range []byte(key) { + hash = (31*hash + int64(b)) % modulus + } + return int(hash) +} + +/* XOR ハッシュ */ +func xorHash(key string) int { + hash := 0 + modulus := 1000000007 + for _, b := range []byte(key) { + fmt.Println(int(b)) + hash ^= int(b) + hash = (31*hash + int(b)) % modulus + } + return hash & modulus +} + +/* 回転ハッシュ */ +func rotHash(key string) int { + var hash int64 + var modulus int64 + + modulus = 1000000007 + for _, b := range []byte(key) { + hash = ((hash << 4) ^ (hash >> 28) ^ int64(b)) % modulus + } + return int(hash) +} diff --git a/ja/codes/go/chapter_heap/heap.go b/ja/codes/go/chapter_heap/heap.go new file mode 100644 index 000000000..9662aacd7 --- /dev/null +++ b/ja/codes/go/chapter_heap/heap.go @@ -0,0 +1,45 @@ +// File: heap.go +// Created Time: 2023-01-12 +// Author: Reanon (793584285@qq.com) + +package chapter_heap + +// Go では heap.Interface を実装することで整数の最大ヒープを構築できる +// heap.Interface を実装するには、同時に sort.Interface も実装する必要がある +type intHeap []any + +// Push は heap.Interface の関数で、要素をヒープに追加する +func (h *intHeap) Push(x any) { + // Push と Pop は pointer receiver を使う + // これらはスライスの内容を調整するだけでなく、スライスの長さも変更するためである。 + *h = append(*h, x.(int)) +} + +// Pop は heap.Interface の関数で、ヒープの先頭要素を取り出す +func (h *intHeap) Pop() any { + // ヒープから取り出す要素を末尾に置く + last := (*h)[len(*h)-1] + *h = (*h)[:len(*h)-1] + return last +} + +// Len は sort.Interface の関数 +func (h *intHeap) Len() int { + return len(*h) +} + +// Less は sort.Interface の関数 +func (h *intHeap) Less(i, j int) bool { + // 最小ヒープを実装する場合は、不等号を < に調整する + return (*h)[i].(int) > (*h)[j].(int) +} + +// Swap は sort.Interface の関数 +func (h *intHeap) Swap(i, j int) { + (*h)[i], (*h)[j] = (*h)[j], (*h)[i] +} + +// Top ヒープ先頭要素を取得 +func (h *intHeap) Top() any { + return (*h)[0] +} diff --git a/ja/codes/go/chapter_heap/heap_test.go b/ja/codes/go/chapter_heap/heap_test.go new file mode 100644 index 000000000..549a503bd --- /dev/null +++ b/ja/codes/go/chapter_heap/heap_test.go @@ -0,0 +1,101 @@ +// File: heap_test.go +// Created Time: 2023-01-12 +// Author: Reanon (793584285@qq.com) + +package chapter_heap + +import ( + "container/heap" + "fmt" + "strconv" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func testPush(h *intHeap, val int) { + // heap.Interface の関数を呼び出して要素を追加する + heap.Push(h, val) + fmt.Printf("\n要素 %d をヒープに追加後 \n", val) + PrintHeap(*h) +} + +func testPop(h *intHeap) { + // heap.Interface の関数を呼び出して要素を削除する + val := heap.Pop(h) + fmt.Printf("\nヒープ先頭要素 %d を取り出した後 \n", val) + PrintHeap(*h) +} + +func TestHeap(t *testing.T) { + /* ヒープを初期化 */ + // 最大ヒープを初期化 + maxHeap := &intHeap{} + heap.Init(maxHeap) + /* 要素をヒープに追加 */ + testPush(maxHeap, 1) + testPush(maxHeap, 3) + testPush(maxHeap, 2) + testPush(maxHeap, 5) + testPush(maxHeap, 4) + + /* ヒープ頂点の要素を取得 */ + top := maxHeap.Top() + fmt.Printf("ヒープ先頭要素は %d\n", top) + + /* ヒープ頂点の要素を取り出す */ + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + + /* ヒープのサイズを取得 */ + size := len(*maxHeap) + fmt.Printf("ヒープの要素数は %d\n", size) + + /* ヒープが空かどうかを判定 */ + isEmpty := len(*maxHeap) == 0 + fmt.Printf("ヒープが空か %t\n", isEmpty) +} + +func TestMyHeap(t *testing.T) { + /* ヒープを初期化 */ + // 最大ヒープを初期化 + maxHeap := newMaxHeap([]any{9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}) + fmt.Printf("入力配列からヒープを構築した後\n") + maxHeap.print() + + /* ヒープ頂点の要素を取得 */ + peek := maxHeap.peek() + fmt.Printf("\nヒープ先頭要素は %d\n", peek) + + /* 要素をヒープに追加 */ + val := 7 + maxHeap.push(val) + fmt.Printf("\n要素 %d をヒープに追加した後\n", val) + maxHeap.print() + + /* ヒープ頂点の要素を取り出す */ + peek = maxHeap.pop() + fmt.Printf("\nヒープ先頭要素 %d を取り出した後\n", peek) + maxHeap.print() + + /* ヒープのサイズを取得 */ + size := maxHeap.size() + fmt.Printf("\nヒープの要素数は %d\n", size) + + /* ヒープが空かどうかを判定 */ + isEmpty := maxHeap.isEmpty() + fmt.Printf("\nヒープが空か %t\n", isEmpty) +} + +func TestTopKHeap(t *testing.T) { + /* ヒープを初期化 */ + // 最大ヒープを初期化 + nums := []int{1, 7, 6, 3, 2} + k := 3 + res := topKHeap(nums, k) + fmt.Printf("最大の " + strconv.Itoa(k) + " 個の要素は") + PrintHeap(*res) +} diff --git a/ja/codes/go/chapter_heap/my_heap.go b/ja/codes/go/chapter_heap/my_heap.go new file mode 100644 index 000000000..629ea7e4c --- /dev/null +++ b/ja/codes/go/chapter_heap/my_heap.go @@ -0,0 +1,140 @@ +// File: my_heap.go +// Created Time: 2023-01-12 +// Author: Reanon (793584285@qq.com) + +package chapter_heap + +import ( + "fmt" + + . "github.com/krahets/hello-algo/pkg" +) + +type maxHeap struct { + // 配列ではなくスライスを使うことで、拡張を考慮せずに済む + data []any +} + +/* コンストラクタ。空のヒープを作成する */ +func newHeap() *maxHeap { + return &maxHeap{ + data: make([]any, 0), + } +} + +/* コンストラクタ。スライスからヒープを構築する */ +func newMaxHeap(nums []any) *maxHeap { + // リスト要素をそのままヒープに追加 + h := &maxHeap{data: nums} + for i := h.parent(len(h.data) - 1); i >= 0; i-- { + // 葉ノード以外のすべてのノードをヒープ化 + h.siftDown(i) + } + return h +} + +/* 左子ノードのインデックスを取得 */ +func (h *maxHeap) left(i int) int { + return 2*i + 1 +} + +/* 右子ノードのインデックスを取得 */ +func (h *maxHeap) right(i int) int { + return 2*i + 2 +} + +/* 親ノードのインデックスを取得 */ +func (h *maxHeap) parent(i int) int { + // 切り捨て除算 + return (i - 1) / 2 +} + +/* 要素を交換 */ +func (h *maxHeap) swap(i, j int) { + h.data[i], h.data[j] = h.data[j], h.data[i] +} + +/* ヒープのサイズを取得 */ +func (h *maxHeap) size() int { + return len(h.data) +} + +/* ヒープが空かどうかを判定 */ +func (h *maxHeap) isEmpty() bool { + return len(h.data) == 0 +} + +/* ヒープ先頭要素にアクセス */ +func (h *maxHeap) peek() any { + return h.data[0] +} + +/* 要素をヒープに追加 */ +func (h *maxHeap) push(val any) { + // ノードを追加 + h.data = append(h.data, val) + // 下から上へヒープ化 + h.siftUp(len(h.data) - 1) +} + +/* ノード i から始めて、下から上へヒープ化 */ +func (h *maxHeap) siftUp(i int) { + for true { + // ノード i の親ノードを取得 + p := h.parent(i) + // 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 + if p < 0 || h.data[i].(int) <= h.data[p].(int) { + break + } + // 2 つのノードを交換 + h.swap(i, p) + // ループで下から上へヒープ化 + i = p + } +} + +/* 要素をヒープから取り出す */ +func (h *maxHeap) pop() any { + // 空判定の処理 + if h.isEmpty() { + fmt.Println("error") + return nil + } + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + h.swap(0, h.size()-1) + // ノードを削除 + val := h.data[len(h.data)-1] + h.data = h.data[:len(h.data)-1] + // 上から下へヒープ化 + h.siftDown(0) + + // ヒープ先頭要素を返す + return val +} + +/* ノード i から始めて、上から下へヒープ化 */ +func (h *maxHeap) siftDown(i int) { + for true { + // ノード i, l, r のうち値が最大のノードを max とする + l, r, max := h.left(i), h.right(i), i + if l < h.size() && h.data[l].(int) > h.data[max].(int) { + max = l + } + if r < h.size() && h.data[r].(int) > h.data[max].(int) { + max = r + } + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + if max == i { + break + } + // 2 つのノードを交換 + h.swap(i, max) + // ループで上から下へヒープ化 + i = max + } +} + +/* ヒープ(二分木)を出力 */ +func (h *maxHeap) print() { + PrintHeap(h.data) +} diff --git a/ja/codes/go/chapter_heap/top_k.go b/ja/codes/go/chapter_heap/top_k.go new file mode 100644 index 000000000..73d47f898 --- /dev/null +++ b/ja/codes/go/chapter_heap/top_k.go @@ -0,0 +1,51 @@ +// File: top_k.go +// Created Time: 2023-06-24 +// Author: Reanon (793584285@qq.com) + +package chapter_heap + +import "container/heap" + +type minHeap []any + +func (h *minHeap) Len() int { return len(*h) } +func (h *minHeap) Less(i, j int) bool { return (*h)[i].(int) < (*h)[j].(int) } +func (h *minHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } + +// Push は heap.Interface のメソッドで、要素をヒープに追加する +func (h *minHeap) Push(x any) { + *h = append(*h, x.(int)) +} + +// Pop は heap.Interface のメソッドで、ヒープの先頭要素を取り出す +func (h *minHeap) Pop() any { + // ヒープから取り出す要素を末尾に置く + last := (*h)[len(*h)-1] + *h = (*h)[:len(*h)-1] + return last +} + +// Top ヒープ先頭要素を取得 +func (h *minHeap) Top() any { + return (*h)[0] +} + +/* ヒープに基づいて配列中の最大の k 個の要素を探す */ +func topKHeap(nums []int, k int) *minHeap { + // 最小ヒープを初期化 + h := &minHeap{} + heap.Init(h) + // 配列の先頭 k 個の要素をヒープに追加 + for i := 0; i < k; i++ { + heap.Push(h, nums[i]) + } + // k+1 番目の要素から開始し、ヒープ長を k に保つ + for i := k; i < len(nums); i++ { + // 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する + if nums[i] > h.Top().(int) { + heap.Pop(h) + heap.Push(h, nums[i]) + } + } + return h +} diff --git a/ja/codes/go/chapter_searching/binary_search.go b/ja/codes/go/chapter_searching/binary_search.go new file mode 100644 index 000000000..9d5154eee --- /dev/null +++ b/ja/codes/go/chapter_searching/binary_search.go @@ -0,0 +1,43 @@ +// File: binary_search.go +// Created Time: 2022-12-05 +// Author: Slone123c (274325721@qq.com) + +package chapter_searching + +/* 二分探索(両閉区間) */ +func binarySearch(nums []int, target int) int { + // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す + i, j := 0, len(nums)-1 + // ループし、探索区間が空になったら終了する(i > j で空) + for i <= j { + m := i + (j-i)/2 // 中点インデックス m を計算 + if nums[m] < target { // この場合、target は区間 [m+1, j] にある + i = m + 1 + } else if nums[m] > target { // この場合、target は区間 [i, m-1] にある + j = m - 1 + } else { // 目標要素が見つかったらそのインデックスを返す + return m + } + } + // 目標要素が見つからなければ -1 を返す + return -1 +} + +/* 二分探索(左閉右開区間) */ +func binarySearchLCRO(nums []int, target int) int { + // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す + i, j := 0, len(nums) + // ループし、探索区間が空になったら終了する(i = j で空) + for i < j { + m := i + (j-i)/2 // 中点インデックス m を計算 + if nums[m] < target { // この場合、target は区間 [m+1, j) にある + i = m + 1 + } else if nums[m] > target { // この場合、target は区間 [i, m) にある + j = m + } else { // 目標要素が見つかったらそのインデックスを返す + return m + } + } + // 目標要素が見つからなければ -1 を返す + return -1 +} diff --git a/ja/codes/go/chapter_searching/binary_search_edge.go b/ja/codes/go/chapter_searching/binary_search_edge.go new file mode 100644 index 000000000..408924ce1 --- /dev/null +++ b/ja/codes/go/chapter_searching/binary_search_edge.go @@ -0,0 +1,31 @@ +// File: binary_search_edge.go +// Created Time: 2023-08-23 +// Author: Reanon (793584285@qq.com) + +package chapter_searching + +/* 最も左の target を二分探索 */ +func binarySearchLeftEdge(nums []int, target int) int { + // target の挿入位置を探すのと等価 + i := binarySearchInsertion(nums, target) + // target が見つからなければ、-1 を返す + if i == len(nums) || nums[i] != target { + return -1 + } + // target が見つかったら、インデックス i を返す + return i +} + +/* 最も右の target を二分探索 */ +func binarySearchRightEdge(nums []int, target int) int { + // 最左の target + 1 を探す問題に変換する + i := binarySearchInsertion(nums, target+1) + // j は最も右の target を指し、i は target より大きい最初の要素を指す + j := i - 1 + // target が見つからなければ、-1 を返す + if j == -1 || nums[j] != target { + return -1 + } + // target が見つかったら、インデックス j を返す + return j +} diff --git a/ja/codes/go/chapter_searching/binary_search_insertion.go b/ja/codes/go/chapter_searching/binary_search_insertion.go new file mode 100644 index 000000000..0523347df --- /dev/null +++ b/ja/codes/go/chapter_searching/binary_search_insertion.go @@ -0,0 +1,49 @@ +// File: binary_search_insertion.go +// Created Time: 2023-08-23 +// Author: Reanon (793584285@qq.com) + +package chapter_searching + +/* 二分探索で挿入位置を探す(重複要素なし) */ +func binarySearchInsertionSimple(nums []int, target int) int { + // 両閉区間 [0, n-1] を初期化 + i, j := 0, len(nums)-1 + for i <= j { + // 中点インデックス m を計算 + m := i + (j-i)/2 + if nums[m] < target { + // target は区間 [m+1, j] にある + i = m + 1 + } else if nums[m] > target { + // target は区間 [i, m-1] にある + j = m - 1 + } else { + // target が見つかったら、挿入位置 m を返す + return m + } + } + // target が見つからなければ、挿入位置 i を返す + return i +} + +/* 二分探索で挿入位置を探す(重複要素あり) */ +func binarySearchInsertion(nums []int, target int) int { + // 両閉区間 [0, n-1] を初期化 + i, j := 0, len(nums)-1 + for i <= j { + // 中点インデックス m を計算 + m := i + (j-i)/2 + if nums[m] < target { + // target は区間 [m+1, j] にある + i = m + 1 + } else if nums[m] > target { + // target は区間 [i, m-1] にある + j = m - 1 + } else { + // target より小さい最初の要素は区間 [i, m-1] にある + j = m - 1 + } + } + // 挿入位置 i を返す + return i +} diff --git a/ja/codes/go/chapter_searching/binary_search_test.go b/ja/codes/go/chapter_searching/binary_search_test.go new file mode 100644 index 000000000..c3bf78e48 --- /dev/null +++ b/ja/codes/go/chapter_searching/binary_search_test.go @@ -0,0 +1,61 @@ +// File: binary_search_test.go +// Created Time: 2022-12-05 +// Author: Slone123c (274325721@qq.com) + +package chapter_searching + +import ( + "fmt" + "testing" +) + +func TestBinarySearch(t *testing.T) { + var ( + target = 6 + nums = []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} + expected = 2 + ) + // 配列で二分探索を実行 + actual := binarySearch(nums, target) + fmt.Println("対象要素 6 のインデックス =", actual) + if actual != expected { + t.Errorf("対象要素 6 のインデックス = %d, 想定値は %d", actual, expected) + } +} + +func TestBinarySearchEdge(t *testing.T) { + // 重複要素を含む配列 + nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} + fmt.Println("\n配列 nums = ", nums) + + // 二分探索で左端と右端を探す + for _, target := range []int{6, 7} { + index := binarySearchLeftEdge(nums, target) + fmt.Println("最も左の要素", target, "のインデックスは", index) + + index = binarySearchRightEdge(nums, target) + fmt.Println("最も右の要素", target, "のインデックスは", index) + } +} + +func TestBinarySearchInsertion(t *testing.T) { + // 重複要素のない配列 + nums := []int{1, 3, 6, 8, 12, 15, 23, 26, 31, 35} + fmt.Println("配列 nums =", nums) + + // 二分探索で挿入位置を探す + for _, target := range []int{6, 9} { + index := binarySearchInsertionSimple(nums, target) + fmt.Println("要素", target, "の挿入位置のインデックスは", index) + } + + // 重複要素を含む配列 + nums = []int{1, 3, 6, 6, 6, 6, 6, 10, 12, 15} + fmt.Println("\n配列 nums =", nums) + + // 二分探索で挿入位置を探す + for _, target := range []int{2, 6, 20} { + index := binarySearchInsertion(nums, target) + fmt.Println("要素", target, "の挿入位置のインデックスは", index) + } +} diff --git a/ja/codes/go/chapter_searching/hashing_search.go b/ja/codes/go/chapter_searching/hashing_search.go new file mode 100644 index 000000000..328cc1521 --- /dev/null +++ b/ja/codes/go/chapter_searching/hashing_search.go @@ -0,0 +1,29 @@ +// File: hashing_search.go +// Created Time: 2022-12-12 +// Author: Slone123c (274325721@qq.com) + +package chapter_searching + +import . "github.com/krahets/hello-algo/pkg" + +/* ハッシュ探索(配列) */ +func hashingSearchArray(m map[int]int, target int) int { + // ハッシュテーブルの key: 目標要素、value: インデックス + // ハッシュテーブルにこの key がなければ -1 を返す + if index, ok := m[target]; ok { + return index + } else { + return -1 + } +} + +/* ハッシュ探索(連結リスト) */ +func hashingSearchLinkedList(m map[int]*ListNode, target int) *ListNode { + // ハッシュテーブルの key: 対象ノードの値、value: ノードオブジェクト + // ハッシュテーブルにその key がなければ nil を返す + if node, ok := m[target]; ok { + return node + } else { + return nil + } +} diff --git a/ja/codes/go/chapter_searching/hashing_search_test.go b/ja/codes/go/chapter_searching/hashing_search_test.go new file mode 100644 index 000000000..494945520 --- /dev/null +++ b/ja/codes/go/chapter_searching/hashing_search_test.go @@ -0,0 +1,36 @@ +// File: hashing_search_test.go +// Created Time: 2022-12-12 +// Author: Slone123c (274325721@qq.com) + +package chapter_searching + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestHashingSearch(t *testing.T) { + target := 3 + /* ハッシュ探索(配列) */ + nums := []int{1, 5, 3, 2, 4, 7, 5, 9, 10, 8} + // ハッシュテーブルを初期化 + m := make(map[int]int) + for i := 0; i < len(nums); i++ { + m[nums[i]] = i + } + index := hashingSearchArray(m, target) + fmt.Println("対象要素 3 のインデックス = ", index) + + /* ハッシュ探索(連結リスト) */ + head := ArrayToLinkedList(nums) + // ハッシュテーブルを初期化 + m1 := make(map[int]*ListNode) + for head != nil { + m1[head.Val] = head + head = head.Next + } + node := hashingSearchLinkedList(m1, target) + fmt.Println("対象ノード値 3 に対応するノードオブジェクト = ", node) +} diff --git a/ja/codes/go/chapter_searching/linear_search.go b/ja/codes/go/chapter_searching/linear_search.go new file mode 100644 index 000000000..942c96305 --- /dev/null +++ b/ja/codes/go/chapter_searching/linear_search.go @@ -0,0 +1,36 @@ +// File: linear_search.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package chapter_searching + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +/* 線形探索(配列) */ +func linearSearchArray(nums []int, target int) int { + // 配列を走査 + for i := 0; i < len(nums); i++ { + // 目標要素が見つかったらそのインデックスを返す + if nums[i] == target { + return i + } + } + // 目標要素が見つからなければ -1 を返す + return -1 +} + +/* 線形探索(連結リスト) */ +func linearSearchLinkedList(node *ListNode, target int) *ListNode { + // 連結リストを走査 + for node != nil { + // 対象ノードが見つかったら、それを返す + if node.Val == target { + return node + } + node = node.Next + } + // 対象要素が見つからない場合は `nil` を返す + return nil +} diff --git a/ja/codes/go/chapter_searching/linear_search_test.go b/ja/codes/go/chapter_searching/linear_search_test.go new file mode 100644 index 000000000..1a7430817 --- /dev/null +++ b/ja/codes/go/chapter_searching/linear_search_test.go @@ -0,0 +1,26 @@ +// File: linear_search_test.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package chapter_searching + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestLinearSearch(t *testing.T) { + target := 3 + nums := []int{1, 5, 3, 2, 4, 7, 5, 9, 10, 8} + + // 配列で線形探索を行う + index := linearSearchArray(nums, target) + fmt.Println("対象要素 3 のインデックス =", index) + + // 連結リストで線形探索を行う + head := ArrayToLinkedList(nums) + node := linearSearchLinkedList(head, target) + fmt.Println("対象ノード値 3 に対応するノードオブジェクト =", node) +} diff --git a/ja/codes/go/chapter_searching/two_sum.go b/ja/codes/go/chapter_searching/two_sum.go new file mode 100644 index 000000000..130d341ea --- /dev/null +++ b/ja/codes/go/chapter_searching/two_sum.go @@ -0,0 +1,33 @@ +// File: two_sum.go +// Created Time: 2022-11-25 +// Author: reanon (793584285@qq.com) + +package chapter_searching + +/* 方法 1:総当たり列挙 */ +func twoSumBruteForce(nums []int, target int) []int { + size := len(nums) + // 2重ループのため、時間計算量は O(n^2) + for i := 0; i < size-1; i++ { + for j := i + 1; j < size; j++ { + if nums[i]+nums[j] == target { + return []int{i, j} + } + } + } + return nil +} + +/* 方法 2:補助ハッシュテーブル */ +func twoSumHashTable(nums []int, target int) []int { + // 補助ハッシュテーブルを使用し、空間計算量は O(n) + hashTable := map[int]int{} + // 単一ループで、時間計算量は O(n) + for idx, val := range nums { + if preIdx, ok := hashTable[target-val]; ok { + return []int{preIdx, idx} + } + hashTable[val] = idx + } + return nil +} diff --git a/ja/codes/go/chapter_searching/two_sum_test.go b/ja/codes/go/chapter_searching/two_sum_test.go new file mode 100644 index 000000000..4aeab7164 --- /dev/null +++ b/ja/codes/go/chapter_searching/two_sum_test.go @@ -0,0 +1,24 @@ +// File: two_sum_test.go +// Created Time: 2022-11-25 +// Author: reanon (793584285@qq.com) + +package chapter_searching + +import ( + "fmt" + "testing" +) + +func TestTwoSum(t *testing.T) { + // ======= Test Case ======= + nums := []int{2, 7, 11, 15} + target := 13 + + // ====== Driver Code ====== + // 方法 1:総当たり法 + res := twoSumBruteForce(nums, target) + fmt.Println("方法1 res =", res) + // 方法2: ハッシュテーブル + res = twoSumHashTable(nums, target) + fmt.Println("方法2 res =", res) +} diff --git a/ja/codes/go/chapter_sorting/bubble_sort.go b/ja/codes/go/chapter_sorting/bubble_sort.go new file mode 100644 index 000000000..87d92d696 --- /dev/null +++ b/ja/codes/go/chapter_sorting/bubble_sort.go @@ -0,0 +1,38 @@ +// File: bubble_sort.go +// Created Time: 2022-12-06 +// Author: Slone123c (274325721@qq.com) + +package chapter_sorting + +/* バブルソート */ +func bubbleSort(nums []int) { + // 外側のループ:未ソート区間は [0, i] + for i := len(nums) - 1; i > 0; i-- { + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for j := 0; j < i; j++ { + if nums[j] > nums[j+1] { + // nums[j] と nums[j + 1] を交換 + nums[j], nums[j+1] = nums[j+1], nums[j] + } + } + } +} + +/* バブルソート(フラグ最適化) */ +func bubbleSortWithFlag(nums []int) { + // 外側のループ:未ソート区間は [0, i] + for i := len(nums) - 1; i > 0; i-- { + flag := false // フラグを初期化する + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for j := 0; j < i; j++ { + if nums[j] > nums[j+1] { + // nums[j] と nums[j + 1] を交換 + nums[j], nums[j+1] = nums[j+1], nums[j] + flag = true // 交換する要素を記録 + } + } + if flag == false { // このバブル処理で要素交換が一度もなければそのまま終了 + break + } + } +} diff --git a/ja/codes/go/chapter_sorting/bubble_sort_test.go b/ja/codes/go/chapter_sorting/bubble_sort_test.go new file mode 100644 index 000000000..8b540aac1 --- /dev/null +++ b/ja/codes/go/chapter_sorting/bubble_sort_test.go @@ -0,0 +1,20 @@ +// File: bubble_sort_test.go +// Created Time: 2022-12-06 +// Author: Slone123c (274325721@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestBubbleSort(t *testing.T) { + nums := []int{4, 1, 3, 1, 5, 2} + bubbleSort(nums) + fmt.Println("バブルソート完了後 nums = ", nums) + + nums1 := []int{4, 1, 3, 1, 5, 2} + bubbleSortWithFlag(nums1) + fmt.Println("バブルソート完了後 nums1 = ", nums1) +} diff --git a/ja/codes/go/chapter_sorting/bucket_sort.go b/ja/codes/go/chapter_sorting/bucket_sort.go new file mode 100644 index 000000000..e73939771 --- /dev/null +++ b/ja/codes/go/chapter_sorting/bucket_sort.go @@ -0,0 +1,37 @@ +// File: bucket_sort.go +// Created Time: 2023-03-27 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import "sort" + +/* バケットソート */ +func bucketSort(nums []float64) { + // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする + k := len(nums) / 2 + buckets := make([][]float64, k) + for i := 0; i < k; i++ { + buckets[i] = make([]float64, 0) + } + // 1. 配列要素を各バケットに振り分ける + for _, num := range nums { + // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する + i := int(num * float64(k)) + // num をバケット i に追加 + buckets[i] = append(buckets[i], num) + } + // 2. 各バケットをソートする + for i := 0; i < k; i++ { + // 組み込みのスライスソート関数を使う。ほかのソートアルゴリズムに置き換えてもよい + sort.Float64s(buckets[i]) + } + // 3. バケットを走査して結果を結合 + i := 0 + for _, bucket := range buckets { + for _, num := range bucket { + nums[i] = num + i++ + } + } +} diff --git a/ja/codes/go/chapter_sorting/bucket_sort_test.go b/ja/codes/go/chapter_sorting/bucket_sort_test.go new file mode 100644 index 000000000..444afd190 --- /dev/null +++ b/ja/codes/go/chapter_sorting/bucket_sort_test.go @@ -0,0 +1,17 @@ +// File: bucket_sort_test.go +// Created Time: 2023-03-27 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestBucketSort(t *testing.T) { + // 入力データは範囲 [0, 1) の浮動小数点数とする + nums := []float64{0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37} + bucketSort(nums) + fmt.Println("バケットソート完了後 nums = ", nums) +} diff --git a/ja/codes/go/chapter_sorting/counting_sort.go b/ja/codes/go/chapter_sorting/counting_sort.go new file mode 100644 index 000000000..773659153 --- /dev/null +++ b/ja/codes/go/chapter_sorting/counting_sort.go @@ -0,0 +1,68 @@ +// File: counting_sort.go +// Created Time: 2023-03-20 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +type CountingSort struct{} + +/* 計数ソート */ +// 簡易実装のため、オブジェクトのソートには使えない +func countingSortNaive(nums []int) { + // 1. 配列の最大要素 m を求める + m := 0 + for _, num := range nums { + if num > m { + m = num + } + } + // 2. 各数値の出現回数を数える + // counter[num] は num の出現回数を表す + counter := make([]int, m+1) + for _, num := range nums { + counter[num]++ + } + // 3. counter を走査し、各要素を元の配列 nums に書き戻す + for i, num := 0, 0; num < m+1; num++ { + for j := 0; j < counter[num]; j++ { + nums[i] = num + i++ + } + } +} + +/* 計数ソート */ +// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである +func countingSort(nums []int) { + // 1. 配列の最大要素 m を求める + m := 0 + for _, num := range nums { + if num > m { + m = num + } + } + // 2. 各数値の出現回数を数える + // counter[num] は num の出現回数を表す + counter := make([]int, m+1) + for _, num := range nums { + counter[num]++ + } + // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する + // つまり counter[num]-1 は、num が res に最後に現れるインデックス + for i := 0; i < m; i++ { + counter[i+1] += counter[i] + } + // 4. nums を逆順に走査し、各要素を結果配列 res に格納する + // 結果を記録するための配列 res を初期化 + n := len(nums) + res := make([]int, n) + for i := n - 1; i >= 0; i-- { + num := nums[i] + // num を対応するインデックスに配置 + res[counter[num]-1] = num + // 累積和を 1 減らして、次に num を配置するインデックスを得る + counter[num]-- + } + // 結果配列 res で元の配列 nums を上書きする + copy(nums, res) +} diff --git a/ja/codes/go/chapter_sorting/counting_sort_test.go b/ja/codes/go/chapter_sorting/counting_sort_test.go new file mode 100644 index 000000000..9ab26e7a2 --- /dev/null +++ b/ja/codes/go/chapter_sorting/counting_sort_test.go @@ -0,0 +1,20 @@ +// File: counting_sort_test.go +// Created Time: 2023-03-20 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestCountingSort(t *testing.T) { + nums := []int{1, 0, 1, 2, 0, 4, 0, 2, 2, 4} + countingSortNaive(nums) + fmt.Println("カウントソート(オブジェクトはソート不可)完了後 nums = ", nums) + + nums1 := []int{1, 0, 1, 2, 0, 4, 0, 2, 2, 4} + countingSort(nums1) + fmt.Println("カウントソート完了後 nums1 = ", nums1) +} diff --git a/ja/codes/go/chapter_sorting/heap_sort.go b/ja/codes/go/chapter_sorting/heap_sort.go new file mode 100644 index 000000000..e46d7f03d --- /dev/null +++ b/ja/codes/go/chapter_sorting/heap_sort.go @@ -0,0 +1,44 @@ +// File: heap_sort.go +// Created Time: 2023-05-29 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +/* ヒープの長さは n。ノード i から下方向にヒープ化 */ +func siftDown(nums *[]int, n, i int) { + for true { + // ノード i, l, r のうち値が最大のノードを ma とする + l := 2*i + 1 + r := 2*i + 2 + ma := i + if l < n && (*nums)[l] > (*nums)[ma] { + ma = l + } + if r < n && (*nums)[r] > (*nums)[ma] { + ma = r + } + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + if ma == i { + break + } + // 2 つのノードを交換 + (*nums)[i], (*nums)[ma] = (*nums)[ma], (*nums)[i] + // ループで上から下へヒープ化 + i = ma + } +} + +/* ヒープソート */ +func heapSort(nums *[]int) { + // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する + for i := len(*nums)/2 - 1; i >= 0; i-- { + siftDown(nums, len(*nums), i) + } + // ヒープから最大要素を取り出し、n-1 回繰り返す + for i := len(*nums) - 1; i > 0; i-- { + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + (*nums)[0], (*nums)[i] = (*nums)[i], (*nums)[0] + // 根ノードを起点に、上から下へヒープ化 + siftDown(nums, i, 0) + } +} diff --git a/ja/codes/go/chapter_sorting/heap_sort_test.go b/ja/codes/go/chapter_sorting/heap_sort_test.go new file mode 100644 index 000000000..712f0c61e --- /dev/null +++ b/ja/codes/go/chapter_sorting/heap_sort_test.go @@ -0,0 +1,16 @@ +// File: heap_sort_test.go +// Created Time: 2023-05-29 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestHeapSort(t *testing.T) { + nums := []int{4, 1, 3, 1, 5, 2} + heapSort(&nums) + fmt.Println("ヒープソート完了後 nums = ", nums) +} diff --git a/ja/codes/go/chapter_sorting/insertion_sort.go b/ja/codes/go/chapter_sorting/insertion_sort.go new file mode 100644 index 000000000..cb6f4fb7f --- /dev/null +++ b/ja/codes/go/chapter_sorting/insertion_sort.go @@ -0,0 +1,20 @@ +// File: insertion_sort.go +// Created Time: 2022-12-12 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +/* 挿入ソート */ +func insertionSort(nums []int) { + // 外側ループ:整列済み区間は [0, i-1] + for i := 1; i < len(nums); i++ { + base := nums[i] + j := i - 1 + // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する + for j >= 0 && nums[j] > base { + nums[j+1] = nums[j] // nums[j] を 1 つ右へ移動する + j-- + } + nums[j+1] = base // base を正しい位置に配置する + } +} diff --git a/ja/codes/go/chapter_sorting/insertion_sort_test.go b/ja/codes/go/chapter_sorting/insertion_sort_test.go new file mode 100644 index 000000000..f3abe6153 --- /dev/null +++ b/ja/codes/go/chapter_sorting/insertion_sort_test.go @@ -0,0 +1,16 @@ +// File: insertion_sort_test.go +// Created Time: 2022-12-12 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestInsertionSort(t *testing.T) { + nums := []int{4, 1, 3, 1, 5, 2} + insertionSort(nums) + fmt.Println("挿入ソート完了後 nums =", nums) +} diff --git a/ja/codes/go/chapter_sorting/merge_sort.go b/ja/codes/go/chapter_sorting/merge_sort.go new file mode 100644 index 000000000..f5609540f --- /dev/null +++ b/ja/codes/go/chapter_sorting/merge_sort.go @@ -0,0 +1,54 @@ +// File: merge_sort.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +/* 左部分配列と右部分配列をマージ */ +func merge(nums []int, left, mid, right int) { + // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] + // マージ結果を格納する一時配列 tmp を作成 + tmp := make([]int, right-left+1) + // 左右の部分配列の開始インデックスを初期化する + i, j, k := left, mid+1, 0 + // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする + for i <= mid && j <= right { + if nums[i] <= nums[j] { + tmp[k] = nums[i] + i++ + } else { + tmp[k] = nums[j] + j++ + } + k++ + } + // 左右の部分配列の残り要素を一時配列にコピーする + for i <= mid { + tmp[k] = nums[i] + i++ + k++ + } + for j <= right { + tmp[k] = nums[j] + j++ + k++ + } + // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする + for k := 0; k < len(tmp); k++ { + nums[left+k] = tmp[k] + } +} + +/* マージソート */ +func mergeSort(nums []int, left, right int) { + // 終了条件 + if left >= right { + return + } + // 分割フェーズ + mid := left + (right - left) / 2 + mergeSort(nums, left, mid) + mergeSort(nums, mid+1, right) + // マージフェーズ + merge(nums, left, mid, right) +} diff --git a/ja/codes/go/chapter_sorting/merge_sort_test.go b/ja/codes/go/chapter_sorting/merge_sort_test.go new file mode 100644 index 000000000..62a62fa00 --- /dev/null +++ b/ja/codes/go/chapter_sorting/merge_sort_test.go @@ -0,0 +1,16 @@ +// File: merge_sort_test.go +// Created Time: 2022-12-13 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestMergeSort(t *testing.T) { + nums := []int{7, 3, 2, 6, 0, 1, 5, 4} + mergeSort(nums, 0, len(nums)-1) + fmt.Println("マージソート完了後 nums = ", nums) +} diff --git a/ja/codes/go/chapter_sorting/quick_sort.go b/ja/codes/go/chapter_sorting/quick_sort.go new file mode 100644 index 000000000..a3cb54ed3 --- /dev/null +++ b/ja/codes/go/chapter_sorting/quick_sort.go @@ -0,0 +1,130 @@ +// File: quick_sort.go +// Created Time: 2022-12-12 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +// クイックソート +type quickSort struct{} + +// クイックソート(中央値の基準値で最適化) +type quickSortMedian struct{} + +// クイックソート(再帰深度最適化) +type quickSortTailCall struct{} + +/* 番兵分割 */ +func (q *quickSort) partition(nums []int, left, right int) int { + // nums[left] を基準値とする + i, j := left, right + for i < j { + for i < j && nums[j] >= nums[left] { + j-- // 右から左へ基準値未満の最初の要素を探す + } + for i < j && nums[i] <= nums[left] { + i++ // 左から右へ基準値より大きい最初の要素を探す + } + // 要素の交換 + nums[i], nums[j] = nums[j], nums[i] + } + // 基準値を 2 つの部分配列の境界へ交換する + nums[i], nums[left] = nums[left], nums[i] + return i // 基準値のインデックスを返す +} + +/* クイックソート */ +func (q *quickSort) quickSort(nums []int, left, right int) { + // 部分配列の長さが 1 なら再帰を終了する + if left >= right { + return + } + // 番兵分割 + pivot := q.partition(nums, left, right) + // 左右の部分配列を再帰処理 + q.quickSort(nums, left, pivot-1) + q.quickSort(nums, pivot+1, right) +} + +/* 3つの候補要素の中央値を選ぶ */ +func (q *quickSortMedian) medianThree(nums []int, left, mid, right int) int { + l, m, r := nums[left], nums[mid], nums[right] + if (l <= m && m <= r) || (r <= m && m <= l) { + return mid // m は l と r の間 + } + if (m <= l && l <= r) || (r <= l && l <= m) { + return left // l は m と r の間 + } + return right +} + +/* 番兵による分割処理(3 点中央値) */ +func (q *quickSortMedian) partition(nums []int, left, right int) int { + // nums[left] を基準値とする + med := q.medianThree(nums, left, (left+right)/2, right) + // 中央値を配列の最左端に交換する + nums[left], nums[med] = nums[med], nums[left] + // nums[left] を基準値とする + i, j := left, right + for i < j { + for i < j && nums[j] >= nums[left] { + j-- // 右から左へ基準値未満の最初の要素を探す + } + for i < j && nums[i] <= nums[left] { + i++ // 左から右へ基準値より大きい最初の要素を探す + } + // 要素の交換 + nums[i], nums[j] = nums[j], nums[i] + } + // 基準値を 2 つの部分配列の境界へ交換する + nums[i], nums[left] = nums[left], nums[i] + return i // 基準値のインデックスを返す +} + +/* クイックソート */ +func (q *quickSortMedian) quickSort(nums []int, left, right int) { + // 部分配列の長さが 1 なら再帰を終了する + if left >= right { + return + } + // 番兵分割 + pivot := q.partition(nums, left, right) + // 左右の部分配列を再帰処理 + q.quickSort(nums, left, pivot-1) + q.quickSort(nums, pivot+1, right) +} + +/* 番兵分割 */ +func (q *quickSortTailCall) partition(nums []int, left, right int) int { + // nums[left] を基準値とする + i, j := left, right + for i < j { + for i < j && nums[j] >= nums[left] { + j-- // 右から左へ基準値未満の最初の要素を探す + } + for i < j && nums[i] <= nums[left] { + i++ // 左から右へ基準値より大きい最初の要素を探す + } + // 要素の交換 + nums[i], nums[j] = nums[j], nums[i] + } + // 基準値を 2 つの部分配列の境界へ交換する + nums[i], nums[left] = nums[left], nums[i] + return i // 基準値のインデックスを返す +} + +/* クイックソート(再帰深度最適化) */ +func (q *quickSortTailCall) quickSort(nums []int, left, right int) { + // 部分配列の長さが 1 なら終了 + for left < right { + // 番兵による分割処理 + pivot := q.partition(nums, left, right) + // 2 つの部分配列のうち短いほうにクイックソートを適用する + if pivot-left < right-pivot { + q.quickSort(nums, left, pivot-1) // 左部分配列を再帰的にソート + left = pivot + 1 // 未ソート区間の残りは [pivot + 1, right] + } else { + q.quickSort(nums, pivot+1, right) // 右部分配列を再帰的にソート + right = pivot - 1 // 未ソート区間の残りは [left, pivot - 1] + } + } +} diff --git a/ja/codes/go/chapter_sorting/quick_sort_test.go b/ja/codes/go/chapter_sorting/quick_sort_test.go new file mode 100644 index 000000000..0eb6fb62a --- /dev/null +++ b/ja/codes/go/chapter_sorting/quick_sort_test.go @@ -0,0 +1,34 @@ +// File: quick_sort_test.go +// Created Time: 2022-12-12 +// Author: msk397 (machangxinq@gmail.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +// クイックソート +func TestQuickSort(t *testing.T) { + q := quickSort{} + nums := []int{4, 1, 3, 1, 5, 2} + q.quickSort(nums, 0, len(nums)-1) + fmt.Println("クイックソート完了後 nums = ", nums) +} + +// クイックソート(中央値の基準値で最適化) +func TestQuickSortMedian(t *testing.T) { + q := quickSortMedian{} + nums := []int{4, 1, 3, 1, 5, 2} + q.quickSort(nums, 0, len(nums)-1) + fmt.Println("クイックソート(中央値ピボット最適化)完了後 nums = ", nums) +} + +// クイックソート(再帰深度最適化) +func TestQuickSortTailCall(t *testing.T) { + q := quickSortTailCall{} + nums := []int{4, 1, 3, 1, 5, 2} + q.quickSort(nums, 0, len(nums)-1) + fmt.Println("クイックソート(再帰深度最適化)完了後 nums = ", nums) +} diff --git a/ja/codes/go/chapter_sorting/radix_sort.go b/ja/codes/go/chapter_sorting/radix_sort.go new file mode 100644 index 000000000..82acc8347 --- /dev/null +++ b/ja/codes/go/chapter_sorting/radix_sort.go @@ -0,0 +1,60 @@ +// File: radix_sort.go +// Created Time: 2023-01-18 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import "math" + +/* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */ +func digit(num, exp int) int { + // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す + return (num / exp) % 10 +} + +/* 計数ソート(nums の k 桁目でソート) */ +func countingSortDigit(nums []int, exp int) { + // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 + counter := make([]int, 10) + n := len(nums) + // 0~9 の各数字の出現回数を集計する + for i := 0; i < n; i++ { + d := digit(nums[i], exp) // nums[i] の第 k 位を取得し、d とする + counter[d]++ // 数字 d の出現回数を数える + } + // 累積和を求め、「出現回数」を「配列インデックス」に変換する + for i := 1; i < 10; i++ { + counter[i] += counter[i-1] + } + // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する + res := make([]int, n) + for i := n - 1; i >= 0; i-- { + d := digit(nums[i], exp) + j := counter[d] - 1 // d の配列内インデックス j を取得する + res[j] = nums[i] // 現在の要素をインデックス j に格納する + counter[d]-- // d の個数を 1 減らす + } + // 結果で元の配列 nums を上書きする + for i := 0; i < n; i++ { + nums[i] = res[i] + } +} + +/* 基数ソート */ +func radixSort(nums []int) { + // 最大桁数の判定用に配列の最大要素を取得 + max := math.MinInt + for _, num := range nums { + if num > max { + max = num + } + } + // 下位桁から上位桁の順に走査する + for exp := 1; max >= exp; exp *= 10 { + // 配列要素の k 桁目に対して計数ソートを行う + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // つまり exp = 10^(k-1) + countingSortDigit(nums, exp) + } +} diff --git a/ja/codes/go/chapter_sorting/radix_sort_test.go b/ja/codes/go/chapter_sorting/radix_sort_test.go new file mode 100644 index 000000000..a37e99213 --- /dev/null +++ b/ja/codes/go/chapter_sorting/radix_sort_test.go @@ -0,0 +1,18 @@ +// File: radix_sort_test.go +// Created Time: 2023-01-18 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestRadixSort(t *testing.T) { + /* 基数ソート */ + nums := []int{10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996} + radixSort(nums) + fmt.Println("基数ソート完了後 nums = ", nums) +} diff --git a/ja/codes/go/chapter_sorting/selection_sort.go b/ja/codes/go/chapter_sorting/selection_sort.go new file mode 100644 index 000000000..1d48a11fd --- /dev/null +++ b/ja/codes/go/chapter_sorting/selection_sort.go @@ -0,0 +1,24 @@ +// File: selection_sort.go +// Created Time: 2023-05-29 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +/* 選択ソート */ +func selectionSort(nums []int) { + n := len(nums) + // 外側ループ:未整列区間は [i, n-1] + for i := 0; i < n-1; i++ { + // 内側のループ:未ソート区間の最小要素を見つける + k := i + for j := i + 1; j < n; j++ { + if nums[j] < nums[k] { + // 最小要素のインデックスを記録 + k = j + } + } + // その最小要素を未整列区間の先頭要素と交換する + nums[i], nums[k] = nums[k], nums[i] + + } +} diff --git a/ja/codes/go/chapter_sorting/selection_sort_test.go b/ja/codes/go/chapter_sorting/selection_sort_test.go new file mode 100644 index 000000000..29762acb9 --- /dev/null +++ b/ja/codes/go/chapter_sorting/selection_sort_test.go @@ -0,0 +1,16 @@ +// File: selection_sort_test.go +// Created Time: 2023-05-29 +// Author: Reanon (793584285@qq.com) + +package chapter_sorting + +import ( + "fmt" + "testing" +) + +func TestSelectionSort(t *testing.T) { + nums := []int{4, 1, 3, 1, 5, 2} + selectionSort(nums) + fmt.Println("選択ソート完了後 nums = ", nums) +} diff --git a/ja/codes/go/chapter_stack_and_queue/array_deque.go b/ja/codes/go/chapter_stack_and_queue/array_deque.go new file mode 100644 index 000000000..60ac9007a --- /dev/null +++ b/ja/codes/go/chapter_stack_and_queue/array_deque.go @@ -0,0 +1,121 @@ +// File: array_deque.go +// Created Time: 2023-03-13 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import "fmt" + +/* 循環配列ベースの両端キュー */ +type arrayDeque struct { + nums []int // 両端キューの要素を格納する配列 + front int // 先頭ポインタ。先頭要素を指す + queSize int // 両端キューの長さ + queCapacity int // キュー容量(格納できる要素数の上限) +} + +/* キューを初期化 */ +func newArrayDeque(queCapacity int) *arrayDeque { + return &arrayDeque{ + nums: make([]int, queCapacity), + queCapacity: queCapacity, + front: 0, + queSize: 0, + } +} + +/* 両端キューの長さを取得 */ +func (q *arrayDeque) size() int { + return q.queSize +} + +/* 両端キューが空かどうかを判定 */ +func (q *arrayDeque) isEmpty() bool { + return q.queSize == 0 +} + +/* 循環配列のインデックスを計算 */ +func (q *arrayDeque) index(i int) int { + // 剰余演算により配列の先頭と末尾をつなげる + // i が配列の末尾を越えたら先頭に戻る + // i が配列の先頭を越えて前に出たら末尾に戻る + return (i + q.queCapacity) % q.queCapacity +} + +/* キュー先頭にエンキュー */ +func (q *arrayDeque) pushFirst(num int) { + if q.queSize == q.queCapacity { + fmt.Println("両端キューは満杯です") + return + } + // 先頭ポインタを左に 1 つ移動する + // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする + q.front = q.index(q.front - 1) + // num をキュー先頭に追加 + q.nums[q.front] = num + q.queSize++ +} + +/* キュー末尾にエンキュー */ +func (q *arrayDeque) pushLast(num int) { + if q.queSize == q.queCapacity { + fmt.Println("両端キューは満杯です") + return + } + // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す + rear := q.index(q.front + q.queSize) + // num をキュー末尾に追加 + q.nums[rear] = num + q.queSize++ +} + +/* キュー先頭からデキュー */ +func (q *arrayDeque) popFirst() any { + num := q.peekFirst() + if num == nil { + return nil + } + // 先頭ポインタを 1 つ後ろへ進める + q.front = q.index(q.front + 1) + q.queSize-- + return num +} + +/* キュー末尾からデキュー */ +func (q *arrayDeque) popLast() any { + num := q.peekLast() + if num == nil { + return nil + } + q.queSize-- + return num +} + +/* キュー先頭の要素にアクセス */ +func (q *arrayDeque) peekFirst() any { + if q.isEmpty() { + return nil + } + return q.nums[q.front] +} + +/* キュー末尾の要素にアクセス */ +func (q *arrayDeque) peekLast() any { + if q.isEmpty() { + return nil + } + // 末尾要素のインデックスを計算 + last := q.index(q.front + q.queSize - 1) + return q.nums[last] +} + +/* 表示用に Slice を取得 */ +func (q *arrayDeque) toSlice() []int { + // 有効長の範囲内のリスト要素のみを変換 + res := make([]int, q.queSize) + for i, j := 0, q.front; i < q.queSize; i++ { + res[i] = q.nums[q.index(j)] + j++ + } + return res +} diff --git a/ja/codes/go/chapter_stack_and_queue/array_queue.go b/ja/codes/go/chapter_stack_and_queue/array_queue.go new file mode 100644 index 000000000..7c6243df8 --- /dev/null +++ b/ja/codes/go/chapter_stack_and_queue/array_queue.go @@ -0,0 +1,78 @@ +// File: array_queue.go +// Created Time: 2022-11-28 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +/* 循環配列ベースのキュー */ +type arrayQueue struct { + nums []int // キュー要素を格納する配列 + front int // 先頭ポインタ。先頭要素を指す + queSize int // キューの長さ + queCapacity int // キュー容量(格納できる要素数の上限) +} + +/* キューを初期化 */ +func newArrayQueue(queCapacity int) *arrayQueue { + return &arrayQueue{ + nums: make([]int, queCapacity), + queCapacity: queCapacity, + front: 0, + queSize: 0, + } +} + +/* キューの長さを取得 */ +func (q *arrayQueue) size() int { + return q.queSize +} + +/* キューが空かどうかを判定 */ +func (q *arrayQueue) isEmpty() bool { + return q.queSize == 0 +} + +/* エンキュー */ +func (q *arrayQueue) push(num int) { + // rear == queCapacity のときキューは満杯 + if q.queSize == q.queCapacity { + return + } + // 末尾ポインタを計算し、末尾インデックス + 1 を指す + // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする + rear := (q.front + q.queSize) % q.queCapacity + // num をキュー末尾に追加 + q.nums[rear] = num + q.queSize++ +} + +/* デキュー */ +func (q *arrayQueue) pop() any { + num := q.peek() + if num == nil { + return nil + } + + // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す + q.front = (q.front + 1) % q.queCapacity + q.queSize-- + return num +} + +/* キュー先頭の要素にアクセス */ +func (q *arrayQueue) peek() any { + if q.isEmpty() { + return nil + } + return q.nums[q.front] +} + +/* 表示用に Slice を取得 */ +func (q *arrayQueue) toSlice() []int { + rear := (q.front + q.queSize) + if rear >= q.queCapacity { + rear %= q.queCapacity + return append(q.nums[q.front:], q.nums[:rear]...) + } + return q.nums[q.front:rear] +} diff --git a/ja/codes/go/chapter_stack_and_queue/array_stack.go b/ja/codes/go/chapter_stack_and_queue/array_stack.go new file mode 100644 index 000000000..889f9e450 --- /dev/null +++ b/ja/codes/go/chapter_stack_and_queue/array_stack.go @@ -0,0 +1,55 @@ +// File: array_stack.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +/* 配列ベースのスタック */ +type arrayStack struct { + data []int // データ +} + +/* スタックを初期化 */ +func newArrayStack() *arrayStack { + return &arrayStack{ + // スタックの長さを 0、容量を 16 に設定 + data: make([]int, 0, 16), + } +} + +/* スタックの長さ */ +func (s *arrayStack) size() int { + return len(s.data) +} + +/* スタックが空かどうか */ +func (s *arrayStack) isEmpty() bool { + return s.size() == 0 +} + +/* プッシュ */ +func (s *arrayStack) push(v int) { + // スライスは自動で拡張される + s.data = append(s.data, v) +} + +/* ポップ */ +func (s *arrayStack) pop() any { + val := s.peek() + s.data = s.data[:len(s.data)-1] + return val +} + +/* スタックトップ要素を取得する */ +func (s *arrayStack) peek() any { + if s.isEmpty() { + return nil + } + val := s.data[len(s.data)-1] + return val +} + +/* 表示用に Slice を取得 */ +func (s *arrayStack) toSlice() []int { + return s.data +} diff --git a/ja/codes/go/chapter_stack_and_queue/deque_test.go b/ja/codes/go/chapter_stack_and_queue/deque_test.go new file mode 100644 index 000000000..6c474fc13 --- /dev/null +++ b/ja/codes/go/chapter_stack_and_queue/deque_test.go @@ -0,0 +1,141 @@ +// File: deque_test.go +// Created Time: 2022-11-29 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "container/list" + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestDeque(t *testing.T) { + /* 両端キューを初期化 */ + // Go では、list を両端キューとして使う + deque := list.New() + + /* 要素をエンキュー */ + deque.PushBack(2) + deque.PushBack(5) + deque.PushBack(4) + deque.PushFront(3) + deque.PushFront(1) + fmt.Print("両端キュー deque = ") + PrintList(deque) + + /* 要素にアクセス */ + front := deque.Front() + fmt.Println("先頭要素 front =", front.Value) + rear := deque.Back() + fmt.Println("末尾要素 rear =", rear.Value) + + /* 要素をデキュー */ + deque.Remove(front) + fmt.Print("先頭から取り出した要素 front = ", front.Value, "、取り出し後の deque = ") + PrintList(deque) + deque.Remove(rear) + fmt.Print("末尾から取り出した要素 rear = ", rear.Value, "、取り出し後の deque = ") + PrintList(deque) + + /* 両端キューの長さを取得 */ + size := deque.Len() + fmt.Println("両端キューの長さ size =", size) + + /* 両端キューが空かどうかを判定 */ + isEmpty := deque.Len() == 0 + fmt.Println("両端キューが空か =", isEmpty) +} + +func TestArrayDeque(t *testing.T) { + /* 両端キューを初期化 */ + // Go では、list を両端キューとして使う + deque := newArrayDeque(16) + + /* 要素をエンキュー */ + deque.pushLast(3) + deque.pushLast(2) + deque.pushLast(5) + fmt.Print("両端キュー deque = ") + PrintSlice(deque.toSlice()) + + /* 要素にアクセス */ + peekFirst := deque.peekFirst() + fmt.Println("先頭要素 peekFirst =", peekFirst) + peekLast := deque.peekLast() + fmt.Println("末尾要素 peekLast =", peekLast) + + /* 要素をエンキュー */ + deque.pushLast(4) + fmt.Print("要素 4 を末尾に追加した後 deque = ") + PrintSlice(deque.toSlice()) + deque.pushFirst(1) + fmt.Print("要素 1 を先頭に追加した後 deque = ") + PrintSlice(deque.toSlice()) + + /* 要素をデキュー */ + popFirst := deque.popFirst() + fmt.Print("先頭から取り出した要素 popFirst = ", popFirst, "、取り出し後の deque = ") + PrintSlice(deque.toSlice()) + popLast := deque.popLast() + fmt.Print("末尾から取り出した要素 popLast = ", popLast, "、取り出し後の deque = ") + PrintSlice(deque.toSlice()) + + /* 両端キューの長さを取得 */ + size := deque.size() + fmt.Println("両端キューの長さ size =", size) + + /* 両端キューが空かどうかを判定 */ + isEmpty := deque.isEmpty() + fmt.Println("両端キューが空か =", isEmpty) +} + +func TestLinkedListDeque(t *testing.T) { + // キューを初期化 + deque := newLinkedListDeque() + + // 要素をエンキュー + deque.pushLast(2) + deque.pushLast(5) + deque.pushLast(4) + deque.pushFirst(3) + deque.pushFirst(1) + fmt.Print("キュー deque = ") + PrintList(deque.toList()) + + // キュー先頭の要素にアクセス + front := deque.peekFirst() + fmt.Println("先頭要素 front =", front) + rear := deque.peekLast() + fmt.Println("末尾要素 rear =", rear) + + // 要素をデキュー + popFirst := deque.popFirst() + fmt.Print("先頭から取り出した要素 popFirst = ", popFirst, "、取り出し後の deque = ") + PrintList(deque.toList()) + popLast := deque.popLast() + fmt.Print("末尾から取り出した要素 popLast = ", popLast, "、取り出し後の deque = ") + PrintList(deque.toList()) + + // キューの長さを取得 + size := deque.size() + fmt.Println("キューの長さ size =", size) + + // 空かどうかを判定 + isEmpty := deque.isEmpty() + fmt.Println("キューが空か =", isEmpty) +} + +// BenchmarkLinkedListDeque 67.92 ns/op in Mac M1 Pro +func BenchmarkLinkedListDeque(b *testing.B) { + deque := newLinkedListDeque() + // use b.N for looping + for i := 0; i < b.N; i++ { + deque.pushLast(777) + } + for i := 0; i < b.N; i++ { + deque.popFirst() + } +} diff --git a/ja/codes/go/chapter_stack_and_queue/linkedlist_deque.go b/ja/codes/go/chapter_stack_and_queue/linkedlist_deque.go new file mode 100644 index 000000000..dec8597fd --- /dev/null +++ b/ja/codes/go/chapter_stack_and_queue/linkedlist_deque.go @@ -0,0 +1,85 @@ +// File: linkedlist_deque.go +// Created Time: 2022-11-29 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "container/list" +) + +/* 双方向連結リストベースの両端キュー */ +type linkedListDeque struct { + // 組み込みパッケージ list を使う + data *list.List +} + +/* 両端キューを初期化する */ +func newLinkedListDeque() *linkedListDeque { + return &linkedListDeque{ + data: list.New(), + } +} + +/* キュー先頭に要素を追加する */ +func (s *linkedListDeque) pushFirst(value any) { + s.data.PushFront(value) +} + +/* キュー末尾に要素を追加する */ +func (s *linkedListDeque) pushLast(value any) { + s.data.PushBack(value) +} + +/* 先頭要素を取り出す */ +func (s *linkedListDeque) popFirst() any { + if s.isEmpty() { + return nil + } + e := s.data.Front() + s.data.Remove(e) + return e.Value +} + +/* 末尾要素を取り出す */ +func (s *linkedListDeque) popLast() any { + if s.isEmpty() { + return nil + } + e := s.data.Back() + s.data.Remove(e) + return e.Value +} + +/* キュー先頭の要素にアクセス */ +func (s *linkedListDeque) peekFirst() any { + if s.isEmpty() { + return nil + } + e := s.data.Front() + return e.Value +} + +/* キュー末尾の要素にアクセス */ +func (s *linkedListDeque) peekLast() any { + if s.isEmpty() { + return nil + } + e := s.data.Back() + return e.Value +} + +/* キューの長さを取得 */ +func (s *linkedListDeque) size() int { + return s.data.Len() +} + +/* キューが空かどうかを判定 */ +func (s *linkedListDeque) isEmpty() bool { + return s.data.Len() == 0 +} + +/* 表示用に List を取得 */ +func (s *linkedListDeque) toList() *list.List { + return s.data +} diff --git a/ja/codes/go/chapter_stack_and_queue/linkedlist_queue.go b/ja/codes/go/chapter_stack_and_queue/linkedlist_queue.go new file mode 100644 index 000000000..ec6da79bd --- /dev/null +++ b/ja/codes/go/chapter_stack_and_queue/linkedlist_queue.go @@ -0,0 +1,61 @@ +// File: linkedlist_queue.go +// Created Time: 2022-11-28 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "container/list" +) + +/* 連結リストベースのキュー */ +type linkedListQueue struct { + // 組み込みパッケージ list でキューを実装する + data *list.List +} + +/* キューを初期化 */ +func newLinkedListQueue() *linkedListQueue { + return &linkedListQueue{ + data: list.New(), + } +} + +/* エンキュー */ +func (s *linkedListQueue) push(value any) { + s.data.PushBack(value) +} + +/* デキュー */ +func (s *linkedListQueue) pop() any { + if s.isEmpty() { + return nil + } + e := s.data.Front() + s.data.Remove(e) + return e.Value +} + +/* キュー先頭の要素にアクセス */ +func (s *linkedListQueue) peek() any { + if s.isEmpty() { + return nil + } + e := s.data.Front() + return e.Value +} + +/* キューの長さを取得 */ +func (s *linkedListQueue) size() int { + return s.data.Len() +} + +/* キューが空かどうかを判定 */ +func (s *linkedListQueue) isEmpty() bool { + return s.data.Len() == 0 +} + +/* 表示用に List を取得 */ +func (s *linkedListQueue) toList() *list.List { + return s.data +} diff --git a/ja/codes/go/chapter_stack_and_queue/linkedlist_stack.go b/ja/codes/go/chapter_stack_and_queue/linkedlist_stack.go new file mode 100644 index 000000000..0c32cff6c --- /dev/null +++ b/ja/codes/go/chapter_stack_and_queue/linkedlist_stack.go @@ -0,0 +1,61 @@ +// File: linkedlist_stack.go +// Created Time: 2022-11-28 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "container/list" +) + +/* 連結リストベースのスタック */ +type linkedListStack struct { + // 組み込みパッケージ list でスタックを実装する + data *list.List +} + +/* スタックを初期化 */ +func newLinkedListStack() *linkedListStack { + return &linkedListStack{ + data: list.New(), + } +} + +/* プッシュ */ +func (s *linkedListStack) push(value int) { + s.data.PushBack(value) +} + +/* ポップ */ +func (s *linkedListStack) pop() any { + if s.isEmpty() { + return nil + } + e := s.data.Back() + s.data.Remove(e) + return e.Value +} + +/* スタックトップの要素にアクセス */ +func (s *linkedListStack) peek() any { + if s.isEmpty() { + return nil + } + e := s.data.Back() + return e.Value +} + +/* スタックの長さを取得 */ +func (s *linkedListStack) size() int { + return s.data.Len() +} + +/* スタックが空かどうかを判定 */ +func (s *linkedListStack) isEmpty() bool { + return s.data.Len() == 0 +} + +/* 表示用に List を取得 */ +func (s *linkedListStack) toList() *list.List { + return s.data +} diff --git a/ja/codes/go/chapter_stack_and_queue/queue_test.go b/ja/codes/go/chapter_stack_and_queue/queue_test.go new file mode 100644 index 000000000..b6cf3f07d --- /dev/null +++ b/ja/codes/go/chapter_stack_and_queue/queue_test.go @@ -0,0 +1,146 @@ +// File: queue_test.go +// Created Time: 2022-11-28 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "container/list" + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestQueue(t *testing.T) { + /* キューを初期化 */ + // Go では、list をキューとして使う + queue := list.New() + + /* 要素をエンキュー */ + queue.PushBack(1) + queue.PushBack(3) + queue.PushBack(2) + queue.PushBack(5) + queue.PushBack(4) + fmt.Print("キュー queue = ") + PrintList(queue) + + /* キュー先頭の要素にアクセス */ + peek := queue.Front() + fmt.Println("先頭要素 peek =", peek.Value) + + /* 要素をデキュー */ + pop := queue.Front() + queue.Remove(pop) + fmt.Print("取り出した要素 pop = ", pop.Value, "、取り出し後の queue = ") + PrintList(queue) + + /* キューの長さを取得 */ + size := queue.Len() + fmt.Println("キューの長さ size =", size) + + /* キューが空かどうかを判定 */ + isEmpty := queue.Len() == 0 + fmt.Println("キューが空か =", isEmpty) +} + +func TestArrayQueue(t *testing.T) { + + // キューを初期化し、キューの共通インターフェースを使う + capacity := 10 + queue := newArrayQueue(capacity) + if queue.pop() != nil { + t.Errorf("want:%v,got:%v", nil, queue.pop()) + } + + // 要素をエンキュー + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + fmt.Print("キュー queue = ") + PrintSlice(queue.toSlice()) + + // キュー先頭の要素にアクセス + peek := queue.peek() + fmt.Println("先頭要素 peek =", peek) + + // 要素をデキュー + pop := queue.pop() + fmt.Print("取り出した要素 pop = ", pop, ", 取り出し後 queue = ") + PrintSlice(queue.toSlice()) + + // キューの長さを取得 + size := queue.size() + fmt.Println("キューの長さ size =", size) + + // 空かどうかを判定 + isEmpty := queue.isEmpty() + fmt.Println("キューが空か =", isEmpty) + + /* 循環配列をテストする */ + for i := 0; i < 10; i++ { + queue.push(i) + queue.pop() + fmt.Print("第", i, "回のエンキュー + デキュー後 queue =") + PrintSlice(queue.toSlice()) + } +} + +func TestLinkedListQueue(t *testing.T) { + // キューを初期化する + queue := newLinkedListQueue() + + // 要素をエンキュー + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + fmt.Print("キュー queue = ") + PrintList(queue.toList()) + + // キュー先頭の要素にアクセス + peek := queue.peek() + fmt.Println("先頭要素 peek =", peek) + + // 要素をデキュー + pop := queue.pop() + fmt.Print("取り出した要素 pop = ", pop, ", 取り出し後 queue = ") + PrintList(queue.toList()) + + // キューの長さを取得 + size := queue.size() + fmt.Println("キューの長さ size =", size) + + // 空かどうかを判定 + isEmpty := queue.isEmpty() + fmt.Println("キューが空か =", isEmpty) +} + +// BenchmarkArrayQueue 8 ns/op in Mac M1 Pro +func BenchmarkArrayQueue(b *testing.B) { + capacity := 1000 + queue := newArrayQueue(capacity) + // use b.N for looping + for i := 0; i < b.N; i++ { + queue.push(777) + } + for i := 0; i < b.N; i++ { + queue.pop() + } +} + +// BenchmarkLinkedQueue 62.66 ns/op in Mac M1 Pro +func BenchmarkLinkedQueue(b *testing.B) { + queue := newLinkedListQueue() + // use b.N for looping + for i := 0; i < b.N; i++ { + queue.push(777) + } + for i := 0; i < b.N; i++ { + queue.pop() + } +} diff --git a/ja/codes/go/chapter_stack_and_queue/stack_test.go b/ja/codes/go/chapter_stack_and_queue/stack_test.go new file mode 100644 index 000000000..ea2d2b7bd --- /dev/null +++ b/ja/codes/go/chapter_stack_and_queue/stack_test.go @@ -0,0 +1,130 @@ +// File: stack_test.go +// Created Time: 2022-11-28 +// Author: Reanon (793584285@qq.com) + +package chapter_stack_and_queue + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestStack(t *testing.T) { + /* スタックを初期化 */ + // Go では、Slice をスタックとして使うことが推奨される + var stack []int + + /* 要素をプッシュ */ + stack = append(stack, 1) + stack = append(stack, 3) + stack = append(stack, 2) + stack = append(stack, 5) + stack = append(stack, 4) + fmt.Print("スタック stack = ") + PrintSlice(stack) + + /* スタックトップの要素にアクセス */ + peek := stack[len(stack)-1] + fmt.Println("スタックトップ要素 peek =", peek) + + /* 要素をポップ */ + pop := stack[len(stack)-1] + stack = stack[:len(stack)-1] + fmt.Print("ポップした要素 pop = ", pop, ",ポップ後 stack = ") + PrintSlice(stack) + + /* スタックの長さを取得 */ + size := len(stack) + fmt.Println("スタックの長さ size =", size) + + /* 空かどうかを判定 */ + isEmpty := len(stack) == 0 + fmt.Println("スタックが空かどうか =", isEmpty) +} + +func TestArrayStack(t *testing.T) { + // スタックを初期化し、インターフェース型で受ける + stack := newArrayStack() + + // 要素をプッシュ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + fmt.Print("スタック stack = ") + PrintSlice(stack.toSlice()) + + // スタックトップの要素にアクセス + peek := stack.peek() + fmt.Println("スタックトップ要素 peek =", peek) + + // 要素をポップ + pop := stack.pop() + fmt.Print("ポップした要素 pop = ", pop, ", ポップ後 stack = ") + PrintSlice(stack.toSlice()) + + // スタックの長さを取得 + size := stack.size() + fmt.Println("スタックの長さ size =", size) + + // 空かどうかを判定 + isEmpty := stack.isEmpty() + fmt.Println("スタックが空かどうか =", isEmpty) +} + +func TestLinkedListStack(t *testing.T) { + // スタックを初期化 + stack := newLinkedListStack() + // 要素をプッシュ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + fmt.Print("スタック stack = ") + PrintList(stack.toList()) + + // スタックトップの要素にアクセス + peek := stack.peek() + fmt.Println("スタックトップ要素 peek =", peek) + + // 要素をポップ + pop := stack.pop() + fmt.Print("ポップした要素 pop = ", pop, ", ポップ後 stack = ") + PrintList(stack.toList()) + + // スタックの長さを取得 + size := stack.size() + fmt.Println("スタックの長さ size =", size) + + // 空かどうかを判定 + isEmpty := stack.isEmpty() + fmt.Println("スタックが空かどうか =", isEmpty) +} + +// BenchmarkArrayStack 8 ns/op in Mac M1 Pro +func BenchmarkArrayStack(b *testing.B) { + stack := newArrayStack() + // use b.N for looping + for i := 0; i < b.N; i++ { + stack.push(777) + } + for i := 0; i < b.N; i++ { + stack.pop() + } +} + +// BenchmarkLinkedListStack 65.02 ns/op in Mac M1 Pro +func BenchmarkLinkedListStack(b *testing.B) { + stack := newLinkedListStack() + // use b.N for looping + for i := 0; i < b.N; i++ { + stack.push(777) + } + for i := 0; i < b.N; i++ { + stack.pop() + } +} diff --git a/ja/codes/go/chapter_tree/array_binary_tree.go b/ja/codes/go/chapter_tree/array_binary_tree.go new file mode 100644 index 000000000..ff8c209eb --- /dev/null +++ b/ja/codes/go/chapter_tree/array_binary_tree.go @@ -0,0 +1,101 @@ +// File: array_binary_tree.go +// Created Time: 2023-07-24 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +/* 配列表現による二分木クラス */ +type arrayBinaryTree struct { + tree []any +} + +/* コンストラクタ */ +func newArrayBinaryTree(arr []any) *arrayBinaryTree { + return &arrayBinaryTree{ + tree: arr, + } +} + +/* リスト容量 */ +func (abt *arrayBinaryTree) size() int { + return len(abt.tree) +} + +/* インデックス i のノードの値を取得 */ +func (abt *arrayBinaryTree) val(i int) any { + // インデックスが範囲外なら、空きを表す null を返す + if i < 0 || i >= abt.size() { + return nil + } + return abt.tree[i] +} + +/* インデックス i のノードの左子ノードのインデックスを取得 */ +func (abt *arrayBinaryTree) left(i int) int { + return 2*i + 1 +} + +/* インデックス i のノードの右子ノードのインデックスを取得 */ +func (abt *arrayBinaryTree) right(i int) int { + return 2*i + 2 +} + +/* インデックス i のノードの親ノードのインデックスを取得 */ +func (abt *arrayBinaryTree) parent(i int) int { + return (i - 1) / 2 +} + +/* レベル順走査 */ +func (abt *arrayBinaryTree) levelOrder() []any { + var res []any + // 配列を直接走査する + for i := 0; i < abt.size(); i++ { + if abt.val(i) != nil { + res = append(res, abt.val(i)) + } + } + return res +} + +/* 深さ優先探索 */ +func (abt *arrayBinaryTree) dfs(i int, order string, res *[]any) { + // 空きスロットなら返す + if abt.val(i) == nil { + return + } + // 先行順走査 + if order == "pre" { + *res = append(*res, abt.val(i)) + } + abt.dfs(abt.left(i), order, res) + // 中順走査 + if order == "in" { + *res = append(*res, abt.val(i)) + } + abt.dfs(abt.right(i), order, res) + // 後順走査 + if order == "post" { + *res = append(*res, abt.val(i)) + } +} + +/* 先行順走査 */ +func (abt *arrayBinaryTree) preOrder() []any { + var res []any + abt.dfs(0, "pre", &res) + return res +} + +/* 中順走査 */ +func (abt *arrayBinaryTree) inOrder() []any { + var res []any + abt.dfs(0, "in", &res) + return res +} + +/* 後順走査 */ +func (abt *arrayBinaryTree) postOrder() []any { + var res []any + abt.dfs(0, "post", &res) + return res +} diff --git a/ja/codes/go/chapter_tree/array_binary_tree_test.go b/ja/codes/go/chapter_tree/array_binary_tree_test.go new file mode 100644 index 000000000..6dee568ac --- /dev/null +++ b/ja/codes/go/chapter_tree/array_binary_tree_test.go @@ -0,0 +1,47 @@ +// File: array_binary_tree_test.go +// Created Time: 2023-07-24 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestArrayBinaryTree(t *testing.T) { + // 二分木を初期化 + // ここでは、配列から直接二分木を生成する関数を利用する + arr := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} + root := SliceToTree(arr) + fmt.Println("\n二分木を初期化") + fmt.Println("二分木の配列表現:") + fmt.Println(arr) + fmt.Println("二分木の連結リスト表現:") + PrintTree(root) + + // 配列表現による二分木クラス + abt := newArrayBinaryTree(arr) + + // ノードにアクセス + i := 1 + l := abt.left(i) + r := abt.right(i) + p := abt.parent(i) + fmt.Println("\n現在のノードのインデックスは", i, ",値は", abt.val(i)) + fmt.Println("左の子ノードのインデックスは", l, ",値は", abt.val(l)) + fmt.Println("右の子ノードのインデックスは", r, ",値は", abt.val(r)) + fmt.Println("親ノードのインデックスは", p, ",値は", abt.val(p)) + + // 木を走査 + res := abt.levelOrder() + fmt.Println("\nレベル順走査:", res) + res = abt.preOrder() + fmt.Println("前順走査:", res) + res = abt.inOrder() + fmt.Println("中順走査:", res) + res = abt.postOrder() + fmt.Println("後順走査:", res) +} diff --git a/ja/codes/go/chapter_tree/avl_tree.go b/ja/codes/go/chapter_tree/avl_tree.go new file mode 100644 index 000000000..e894825d2 --- /dev/null +++ b/ja/codes/go/chapter_tree/avl_tree.go @@ -0,0 +1,200 @@ +// File: avl_tree.go +// Created Time: 2023-01-08 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import . "github.com/krahets/hello-algo/pkg" + +/* AVL 木 */ +type aVLTree struct { + // 根ノード + root *TreeNode +} + +func newAVLTree() *aVLTree { + return &aVLTree{root: nil} +} + +/* ノードの高さを取得 */ +func (t *aVLTree) height(node *TreeNode) int { + // 空ノードの高さは -1、葉ノードの高さは 0 + if node != nil { + return node.Height + } + return -1 +} + +/* ノードの高さを更新する */ +func (t *aVLTree) updateHeight(node *TreeNode) { + lh := t.height(node.Left) + rh := t.height(node.Right) + // ノードの高さは最も高い部分木の高さ + 1 に等しい + if lh > rh { + node.Height = lh + 1 + } else { + node.Height = rh + 1 + } +} + +/* 平衡係数を取得 */ +func (t *aVLTree) balanceFactor(node *TreeNode) int { + // 空ノードの平衡係数は 0 + if node == nil { + return 0 + } + // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ + return t.height(node.Left) - t.height(node.Right) +} + +/* 右回転 */ +func (t *aVLTree) rightRotate(node *TreeNode) *TreeNode { + child := node.Left + grandChild := child.Right + // child を支点として node を右回転させる + child.Right = node + node.Left = grandChild + // ノードの高さを更新する + t.updateHeight(node) + t.updateHeight(child) + // 回転後の部分木の根ノードを返す + return child +} + +/* 左回転 */ +func (t *aVLTree) leftRotate(node *TreeNode) *TreeNode { + child := node.Right + grandChild := child.Left + // child を支点として node を左回転させる + child.Left = node + node.Right = grandChild + // ノードの高さを更新する + t.updateHeight(node) + t.updateHeight(child) + // 回転後の部分木の根ノードを返す + return child +} + +/* 回転操作を行い、この部分木の平衡を回復する */ +func (t *aVLTree) rotate(node *TreeNode) *TreeNode { + // ノード `node` の平衡係数を取得する + // Go では短い変数名が推奨されるため、ここで `bf` は `t.balanceFactor` を表す + bf := t.balanceFactor(node) + // 左に偏った木 + if bf > 1 { + if t.balanceFactor(node.Left) >= 0 { + // 右回転 + return t.rightRotate(node) + } else { + // 左回転してから右回転 + node.Left = t.leftRotate(node.Left) + return t.rightRotate(node) + } + } + // 右に偏った木 + if bf < -1 { + if t.balanceFactor(node.Right) <= 0 { + // 左回転 + return t.leftRotate(node) + } else { + // 右回転してから左回転 + node.Right = t.rightRotate(node.Right) + return t.leftRotate(node) + } + } + // 平衡木なので回転不要、そのまま返す + return node +} + +/* ノードを挿入 */ +func (t *aVLTree) insert(val int) { + t.root = t.insertHelper(t.root, val) +} + +/* ノードを再帰的に挿入する(補助関数) */ +func (t *aVLTree) insertHelper(node *TreeNode, val int) *TreeNode { + if node == nil { + return NewTreeNode(val) + } + /* 1. 挿入位置を探索してノードを挿入 */ + if val < node.Val.(int) { + node.Left = t.insertHelper(node.Left, val) + } else if val > node.Val.(int) { + node.Right = t.insertHelper(node.Right, val) + } else { + // 重複ノードは挿入せず、そのまま返す + return node + } + // ノードの高さを更新する + t.updateHeight(node) + /* 2. 回転操作を行い、部分木の平衡を回復する */ + node = t.rotate(node) + // 部分木の根ノードを返す + return node +} + +/* ノードを削除 */ +func (t *aVLTree) remove(val int) { + t.root = t.removeHelper(t.root, val) +} + +/* ノードを再帰的に削除する(補助関数) */ +func (t *aVLTree) removeHelper(node *TreeNode, val int) *TreeNode { + if node == nil { + return nil + } + /* 1. ノードを探索して削除 */ + if val < node.Val.(int) { + node.Left = t.removeHelper(node.Left, val) + } else if val > node.Val.(int) { + node.Right = t.removeHelper(node.Right, val) + } else { + if node.Left == nil || node.Right == nil { + child := node.Left + if node.Right != nil { + child = node.Right + } + if child == nil { + // 子ノード数 = 0 の場合、node をそのまま削除して返す + return nil + } else { + // 子ノード数 = 1 の場合、node をそのまま削除する + node = child + } + } else { + // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える + temp := node.Right + for temp.Left != nil { + temp = temp.Left + } + node.Right = t.removeHelper(node.Right, temp.Val.(int)) + node.Val = temp.Val + } + } + // ノードの高さを更新する + t.updateHeight(node) + /* 2. 回転操作を行い、部分木の平衡を回復する */ + node = t.rotate(node) + // 部分木の根ノードを返す + return node +} + +/* ノードを探索 */ +func (t *aVLTree) search(val int) *TreeNode { + cur := t.root + // ループで探索し、葉ノードを越えたら抜ける + for cur != nil { + if cur.Val.(int) < val { + // 目標ノードは cur の右部分木にある + cur = cur.Right + } else if cur.Val.(int) > val { + // 目標ノードは cur の左部分木にある + cur = cur.Left + } else { + // 目標ノードが見つかったらループを抜ける + break + } + } + // 目標ノードを返す + return cur +} diff --git a/ja/codes/go/chapter_tree/avl_tree_test.go b/ja/codes/go/chapter_tree/avl_tree_test.go new file mode 100644 index 000000000..fe0862b79 --- /dev/null +++ b/ja/codes/go/chapter_tree/avl_tree_test.go @@ -0,0 +1,54 @@ +// File: avl_tree_test.go +// Created Time: 2023-01-08 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestAVLTree(t *testing.T) { + /* 空の AVL 木を初期化する */ + tree := newAVLTree() + /* ノードを挿入 */ + // ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい + testInsert(tree, 1) + testInsert(tree, 2) + testInsert(tree, 3) + testInsert(tree, 4) + testInsert(tree, 5) + testInsert(tree, 8) + testInsert(tree, 7) + testInsert(tree, 9) + testInsert(tree, 10) + testInsert(tree, 6) + + /* 重複ノードを挿入する */ + testInsert(tree, 7) + + /* ノードを削除 */ + // ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい + testRemove(tree, 8) // 次数 0 のノードを削除する + testRemove(tree, 5) // 次数 1 のノードを削除する + testRemove(tree, 4) // 次数 2 のノードを削除する + + /* ノードを検索 */ + node := tree.search(7) + fmt.Printf("\n見つかったノードオブジェクトは %#v ,ノードの値 = %d \n", node, node.Val) +} + +func testInsert(tree *aVLTree, val int) { + tree.insert(val) + fmt.Printf("\nノード %d を挿入後、AVL 木は \n", val) + PrintTree(tree.root) +} + +func testRemove(tree *aVLTree, val int) { + tree.remove(val) + fmt.Printf("\nノード %d を削除後、AVL 木は \n", val) + PrintTree(tree.root) +} diff --git a/ja/codes/go/chapter_tree/binary_search_tree.go b/ja/codes/go/chapter_tree/binary_search_tree.go new file mode 100644 index 000000000..67657f417 --- /dev/null +++ b/ja/codes/go/chapter_tree/binary_search_tree.go @@ -0,0 +1,142 @@ +// File: binary_search_tree.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +type binarySearchTree struct { + root *TreeNode +} + +func newBinarySearchTree() *binarySearchTree { + bst := &binarySearchTree{} + // 空の木を初期化する + bst.root = nil + return bst +} + +/* 根ノードを取得 */ +func (bst *binarySearchTree) getRoot() *TreeNode { + return bst.root +} + +/* ノードを探索 */ +func (bst *binarySearchTree) search(num int) *TreeNode { + node := bst.root + // ループで探索し、葉ノードを越えたら抜ける + for node != nil { + if node.Val.(int) < num { + // 目標ノードは cur の右部分木にある + node = node.Right + } else if node.Val.(int) > num { + // 目標ノードは cur の左部分木にある + node = node.Left + } else { + // 目標ノードが見つかったらループを抜ける + break + } + } + // 目標ノードを返す + return node +} + +/* ノードを挿入 */ +func (bst *binarySearchTree) insert(num int) { + cur := bst.root + // 木が空なら、根ノードを初期化する + if cur == nil { + bst.root = NewTreeNode(num) + return + } + // 挿入対象ノードの直前のノード位置 + var pre *TreeNode = nil + // ループで探索し、葉ノードを越えたら抜ける + for cur != nil { + if cur.Val == num { + return + } + pre = cur + if cur.Val.(int) < num { + cur = cur.Right + } else { + cur = cur.Left + } + } + // ノードを挿入 + node := NewTreeNode(num) + if pre.Val.(int) < num { + pre.Right = node + } else { + pre.Left = node + } +} + +/* ノードを削除 */ +func (bst *binarySearchTree) remove(num int) { + cur := bst.root + // 木が空なら、そのまま早期リターンする + if cur == nil { + return + } + // 削除対象ノードの直前のノード位置 + var pre *TreeNode = nil + // ループで探索し、葉ノードを越えたら抜ける + for cur != nil { + if cur.Val == num { + break + } + pre = cur + if cur.Val.(int) < num { + // 削除対象ノードは右部分木にある + cur = cur.Right + } else { + // 削除対象ノードは左部分木にある + cur = cur.Left + } + } + // 削除対象ノードがなければそのまま返す + if cur == nil { + return + } + // 子ノード数は 0 または 1 + if cur.Left == nil || cur.Right == nil { + var child *TreeNode = nil + // 削除対象ノードの子ノードを取り出す + if cur.Left != nil { + child = cur.Left + } else { + child = cur.Right + } + // ノード cur を削除する + if cur != bst.root { + if pre.Left == cur { + pre.Left = child + } else { + pre.Right = child + } + } else { + // 削除ノードが根ノードなら、根ノードを再設定 + bst.root = child + } + // 子ノード数は 2 + } else { + // 中順走査で削除対象ノード `cur` の次のノードを取得する + tmp := cur.Right + for tmp.Left != nil { + tmp = tmp.Left + } + // ノード tmp を再帰的に削除 + bst.remove(tmp.Val.(int)) + // tmp で cur を上書きする + cur.Val = tmp.Val + } +} + +/* 二分探索木を出力 */ +func (bst *binarySearchTree) print() { + PrintTree(bst.root) +} diff --git a/ja/codes/go/chapter_tree/binary_search_tree_test.go b/ja/codes/go/chapter_tree/binary_search_tree_test.go new file mode 100644 index 000000000..51bfb4ad8 --- /dev/null +++ b/ja/codes/go/chapter_tree/binary_search_tree_test.go @@ -0,0 +1,45 @@ +// File: binary_search_tree_test.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" +) + +func TestBinarySearchTree(t *testing.T) { + bst := newBinarySearchTree() + nums := []int{8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15} + // 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる + for _, num := range nums { + bst.insert(num) + } + fmt.Println("\n初期化した二分木は:") + bst.print() + + // 根ノードを取得 + node := bst.getRoot() + fmt.Println("\n二分木の根ノードは:", node.Val) + + // ノードを探索 + node = bst.search(7) + fmt.Println("見つかったノードオブジェクトは", node, ",ノードの値 =", node.Val) + + // ノードを挿入 + bst.insert(16) + fmt.Println("\nノード 16 を挿入した後の二分木は:") + bst.print() + + // ノードを削除 + bst.remove(1) + fmt.Println("\nノード 1 を削除した後の二分木は:") + bst.print() + bst.remove(2) + fmt.Println("\nノード 2 を削除した後の二分木は:") + bst.print() + bst.remove(4) + fmt.Println("\nノード 4 を削除した後の二分木は:") + bst.print() +} diff --git a/ja/codes/go/chapter_tree/binary_tree_bfs.go b/ja/codes/go/chapter_tree/binary_tree_bfs.go new file mode 100644 index 000000000..50e8d1943 --- /dev/null +++ b/ja/codes/go/chapter_tree/binary_tree_bfs.go @@ -0,0 +1,35 @@ +// File: binary_tree_bfs.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "container/list" + + . "github.com/krahets/hello-algo/pkg" +) + +/* レベル順走査 */ +func levelOrder(root *TreeNode) []any { + // キューを初期化し、ルートノードを追加する + queue := list.New() + queue.PushBack(root) + // 走査順を保存するためのスライスを初期化する + nums := make([]any, 0) + for queue.Len() > 0 { + // デキュー + node := queue.Remove(queue.Front()).(*TreeNode) + // ノードの値を保存する + nums = append(nums, node.Val) + if node.Left != nil { + // 左子ノードをキューに追加 + queue.PushBack(node.Left) + } + if node.Right != nil { + // 右子ノードをキューに追加 + queue.PushBack(node.Right) + } + } + return nums +} diff --git a/ja/codes/go/chapter_tree/binary_tree_bfs_test.go b/ja/codes/go/chapter_tree/binary_tree_bfs_test.go new file mode 100644 index 000000000..c6d26ff3b --- /dev/null +++ b/ja/codes/go/chapter_tree/binary_tree_bfs_test.go @@ -0,0 +1,24 @@ +// File: binary_tree_bfs_test.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestLevelOrder(t *testing.T) { + /* 二分木を初期化 */ + // ここでは、配列から直接二分木を生成する関数を利用する + root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7}) + fmt.Println("\n二分木を初期化: ") + PrintTree(root) + + // レベル順走査 + nums := levelOrder(root) + fmt.Println("\nレベル順走査のノード出力シーケンス =", nums) +} diff --git a/ja/codes/go/chapter_tree/binary_tree_dfs.go b/ja/codes/go/chapter_tree/binary_tree_dfs.go new file mode 100644 index 000000000..eb885b33b --- /dev/null +++ b/ja/codes/go/chapter_tree/binary_tree_dfs.go @@ -0,0 +1,44 @@ +// File: binary_tree_dfs.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + . "github.com/krahets/hello-algo/pkg" +) + +var nums []any + +/* 先行順走査 */ +func preOrder(node *TreeNode) { + if node == nil { + return + } + // 訪問順序:根ノード -> 左部分木 -> 右部分木 + nums = append(nums, node.Val) + preOrder(node.Left) + preOrder(node.Right) +} + +/* 中順走査 */ +func inOrder(node *TreeNode) { + if node == nil { + return + } + // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 + inOrder(node.Left) + nums = append(nums, node.Val) + inOrder(node.Right) +} + +/* 後順走査 */ +func postOrder(node *TreeNode) { + if node == nil { + return + } + // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード + postOrder(node.Left) + postOrder(node.Right) + nums = append(nums, node.Val) +} diff --git a/ja/codes/go/chapter_tree/binary_tree_dfs_test.go b/ja/codes/go/chapter_tree/binary_tree_dfs_test.go new file mode 100644 index 000000000..9acf20e31 --- /dev/null +++ b/ja/codes/go/chapter_tree/binary_tree_dfs_test.go @@ -0,0 +1,35 @@ +// File: binary_tree_dfs_test.go +// Created Time: 2022-11-26 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestPreInPostOrderTraversal(t *testing.T) { + /* 二分木を初期化 */ + // ここでは、配列から直接二分木を生成する関数を利用する + root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7}) + fmt.Println("\n二分木を初期化: ") + PrintTree(root) + + // 先行順走査 + nums = nil + preOrder(root) + fmt.Println("\n前順走査のノード出力シーケンス =", nums) + + // 中順走査 + nums = nil + inOrder(root) + fmt.Println("\n中順走査のノード出力シーケンス =", nums) + + // 後順走査 + nums = nil + postOrder(root) + fmt.Println("\n後順走査のノード出力シーケンス =", nums) +} diff --git a/ja/codes/go/chapter_tree/binary_tree_test.go b/ja/codes/go/chapter_tree/binary_tree_test.go new file mode 100644 index 000000000..a3a65130a --- /dev/null +++ b/ja/codes/go/chapter_tree/binary_tree_test.go @@ -0,0 +1,41 @@ +// File: binary_tree_test.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package chapter_tree + +import ( + "fmt" + "testing" + + . "github.com/krahets/hello-algo/pkg" +) + +func TestBinaryTree(t *testing.T) { + /* 二分木を初期化 */ + // ノードを初期化 + n1 := NewTreeNode(1) + n2 := NewTreeNode(2) + n3 := NewTreeNode(3) + n4 := NewTreeNode(4) + n5 := NewTreeNode(5) + // ノード間の参照(ポインタ)を構築する + n1.Left = n2 + n1.Right = n3 + n2.Left = n4 + n2.Right = n5 + fmt.Println("二分木を初期化") + PrintTree(n1) + + /* ノードの挿入と削除 */ + // ノードを挿入 + p := NewTreeNode(0) + n1.Left = p + p.Left = n2 + fmt.Println("ノード P を挿入後") + PrintTree(n1) + // ノードを削除 + n1.Left = n2 + fmt.Println("ノード P を削除後") + PrintTree(n1) +} diff --git a/ja/codes/go/go.mod b/ja/codes/go/go.mod new file mode 100644 index 000000000..34f5dac20 --- /dev/null +++ b/ja/codes/go/go.mod @@ -0,0 +1,3 @@ +module github.com/krahets/hello-algo + +go 1.19 diff --git a/ja/codes/go/pkg/list_node.go b/ja/codes/go/pkg/list_node.go new file mode 100644 index 000000000..0276f4c10 --- /dev/null +++ b/ja/codes/go/pkg/list_node.go @@ -0,0 +1,31 @@ +// File: list_node.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package pkg + +// ListNode は連結リストノード +type ListNode struct { + Next *ListNode + Val int +} + +// NewListNode は連結リストノードのコンストラクタ +func NewListNode(v int) *ListNode { + return &ListNode{ + Next: nil, + Val: v, + } +} + +// ArrayToLinkedList は配列を連結リストにデシリアライズする +func ArrayToLinkedList(arr []int) *ListNode { + // dummy header of linked list + dummy := NewListNode(0) + node := dummy + for _, val := range arr { + node.Next = NewListNode(val) + node = node.Next + } + return dummy.Next +} diff --git a/ja/codes/go/pkg/list_node_test.go b/ja/codes/go/pkg/list_node_test.go new file mode 100644 index 000000000..e61d8d5bf --- /dev/null +++ b/ja/codes/go/pkg/list_node_test.go @@ -0,0 +1,16 @@ +// File: list_node_test.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package pkg + +import ( + "testing" +) + +func TestListNode(t *testing.T) { + arr := []int{2, 3, 5, 6, 7} + head := ArrayToLinkedList(arr) + + PrintLinkedList(head) +} diff --git a/ja/codes/go/pkg/print_utils.go b/ja/codes/go/pkg/print_utils.go new file mode 100644 index 000000000..f3bca481e --- /dev/null +++ b/ja/codes/go/pkg/print_utils.go @@ -0,0 +1,118 @@ +// File: print_utils.go +// Created Time: 2022-12-03 +// Author: Reanon (793584285@qq.com), krahets (krahets@163.com), msk397 (machangxinq@gmail.com) + +package pkg + +import ( + "container/list" + "fmt" + "strconv" + "strings" +) + +// PrintSlice はスライスを出力する +func PrintSlice[T any](nums []T) { + fmt.Printf("%v", nums) + fmt.Println() +} + +// PrintList はリストを出力する +func PrintList(list *list.List) { + if list.Len() == 0 { + fmt.Print("[]\n") + return + } + e := list.Front() + // string への強制変換は効率に影響する + fmt.Print("[") + for e.Next() != nil { + fmt.Print(e.Value, " ") + e = e.Next() + } + fmt.Print(e.Value, "]\n") +} + +// PrintMap はハッシュテーブルを出力する +func PrintMap[K comparable, V any](m map[K]V) { + for key, value := range m { + fmt.Println(key, "->", value) + } +} + +// PrintHeap はヒープを出力する +func PrintHeap(h []any) { + fmt.Printf("ヒープの配列表現:") + fmt.Printf("%v", h) + fmt.Printf("\nヒープの木構造表示:\n") + root := SliceToTree(h) + PrintTree(root) +} + +// PrintLinkedList は連結リストを出力する +func PrintLinkedList(node *ListNode) { + if node == nil { + return + } + var builder strings.Builder + for node.Next != nil { + builder.WriteString(strconv.Itoa(node.Val) + " -> ") + node = node.Next + } + builder.WriteString(strconv.Itoa(node.Val)) + fmt.Println(builder.String()) +} + +// PrintTree は二分木を出力する +func PrintTree(root *TreeNode) { + printTreeHelper(root, nil, false) +} + +// printTreeHelper は二分木を出力する +// This tree printer is borrowed from TECHIE DELIGHT +// https://www.techiedelight.com/c-program-print-binary-tree/ +func printTreeHelper(root *TreeNode, prev *trunk, isRight bool) { + if root == nil { + return + } + prevStr := " " + trunk := newTrunk(prev, prevStr) + printTreeHelper(root.Right, trunk, true) + if prev == nil { + trunk.str = "———" + } else if isRight { + trunk.str = "/———" + prevStr = " |" + } else { + trunk.str = "\\———" + prev.str = prevStr + } + showTrunk(trunk) + fmt.Println(root.Val) + if prev != nil { + prev.str = prevStr + } + trunk.str = " |" + printTreeHelper(root.Left, trunk, false) +} + +type trunk struct { + prev *trunk + str string +} + +func newTrunk(prev *trunk, str string) *trunk { + return &trunk{ + prev: prev, + str: str, + } +} + +func showTrunk(t *trunk) { + if t == nil { + return + } + + showTrunk(t.prev) + fmt.Print(t.str) +} diff --git a/ja/codes/go/pkg/tree_node.go b/ja/codes/go/pkg/tree_node.go new file mode 100644 index 000000000..85b005ef6 --- /dev/null +++ b/ja/codes/go/pkg/tree_node.go @@ -0,0 +1,78 @@ +// File: tree_node.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package pkg + +// TreeNode は二分木ノード +type TreeNode struct { + Val any // ノード値 + Height int // ノードの高さ + Left *TreeNode // 左子ノードへの参照 + Right *TreeNode // 右子ノードへの参照 +} + +// NewTreeNode は二分木ノードのコンストラクタ +func NewTreeNode(v any) *TreeNode { + return &TreeNode{ + Val: v, + Height: 0, + Left: nil, + Right: nil, + } +} + +// シリアライズの符号化規則は次を参照してください: +// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ +// 二分木の配列表現: +// [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] +// 二分木の連結リスト表現: +// +// /——— 15 +// /——— 7 +// /——— 3 +// | \\——— 6 +// | \\——— 12 +// +// ——— 1 +// +// \\——— 2 +// | /——— 9 +// \\——— 4 +// \\——— 8 + +// SliceToTreeDFS はリストを二分木にデシリアライズする:再帰 +func SliceToTreeDFS(arr []any, i int) *TreeNode { + if i < 0 || i >= len(arr) || arr[i] == nil { + return nil + } + root := NewTreeNode(arr[i]) + root.Left = SliceToTreeDFS(arr, 2*i+1) + root.Right = SliceToTreeDFS(arr, 2*i+2) + return root +} + +// SliceToTree はスライスを二分木にデシリアライズする +func SliceToTree(arr []any) *TreeNode { + return SliceToTreeDFS(arr, 0) +} + +// TreeToSliceDFS は二分木をスライスにシリアライズする:再帰 +func TreeToSliceDFS(root *TreeNode, i int, res *[]any) { + if root == nil { + return + } + for i >= len(*res) { + *res = append(*res, nil) + } + (*res)[i] = root.Val + TreeToSliceDFS(root.Left, 2*i+1, res) + TreeToSliceDFS(root.Right, 2*i+2, res) +} + +// TreeToSlice は二分木をスライスにシリアライズする +func TreeToSlice(root *TreeNode) []any { + var res []any + TreeToSliceDFS(root, 0, &res) + return res +} diff --git a/ja/codes/go/pkg/tree_node_test.go b/ja/codes/go/pkg/tree_node_test.go new file mode 100644 index 000000000..043aab099 --- /dev/null +++ b/ja/codes/go/pkg/tree_node_test.go @@ -0,0 +1,21 @@ +// File: tree_node_test.go +// Created Time: 2022-11-25 +// Author: Reanon (793584285@qq.com) + +package pkg + +import ( + "fmt" + "testing" +) + +func TestTreeNode(t *testing.T) { + arr := []any{1, 2, 3, nil, 5, 6, nil} + node := SliceToTree(arr) + + // print tree + PrintTree(node) + + // tree to arr + fmt.Println(TreeToSlice(node)) +} diff --git a/ja/codes/go/pkg/vertex.go b/ja/codes/go/pkg/vertex.go new file mode 100644 index 000000000..da2c29b5f --- /dev/null +++ b/ja/codes/go/pkg/vertex.go @@ -0,0 +1,55 @@ +// File: vertex.go +// Created Time: 2023-02-18 +// Author: Reanon (793584285@qq.com) + +package pkg + +// Vertex は頂点クラス +type Vertex struct { + Val int +} + +// NewVertex は頂点のコンストラクタ +func NewVertex(val int) Vertex { + return Vertex{ + Val: val, + } +} + +// ValsToVets は値リストを頂点リストにデシリアライズする +func ValsToVets(vals []int) []Vertex { + vets := make([]Vertex, len(vals)) + for i := 0; i < len(vals); i++ { + vets[i] = NewVertex(vals[i]) + } + return vets +} + +// VetsToVals は頂点リストを値リストにシリアライズする +func VetsToVals(vets []Vertex) []int { + vals := make([]int, len(vets)) + for i := range vets { + vals[i] = vets[i].Val + } + return vals +} + +// DeleteSliceElms はスライスの指定要素を削除する +func DeleteSliceElms[T any](a []T, elms ...T) []T { + if len(a) == 0 || len(elms) == 0 { + return a + } + // まず要素を set に変換する + m := make(map[any]struct{}) + for _, v := range elms { + m[v] = struct{}{} + } + // 指定した要素を除外する + res := make([]T, 0, len(a)) + for _, v := range a { + if _, ok := m[v]; !ok { + res = append(res, v) + } + } + return res +} diff --git a/ja/codes/java/.gitignore b/ja/codes/java/.gitignore new file mode 100644 index 000000000..378eac25d --- /dev/null +++ b/ja/codes/java/.gitignore @@ -0,0 +1 @@ +build diff --git a/ja/codes/java/chapter_array_and_linkedlist/array.java b/ja/codes/java/chapter_array_and_linkedlist/array.java index 19f1d0780..f58c075bf 100644 --- a/ja/codes/java/chapter_array_and_linkedlist/array.java +++ b/ja/codes/java/chapter_array_and_linkedlist/array.java @@ -10,20 +10,20 @@ import java.util.*; import java.util.concurrent.ThreadLocalRandom; public class array { - /* 要素へのランダムアクセス */ + /* 要素へランダムアクセス */ static int randomAccess(int[] nums) { - // 区間 [0, nums.length) からランダムに数を選択 + // 区間 [0, nums.length) からランダムに 1 つの数を選ぶ int randomIndex = ThreadLocalRandom.current().nextInt(0, nums.length); - // ランダム要素を取得して返す + // ランダムな要素を取得して返す int randomNum = nums[randomIndex]; return randomNum; } - /* 配列長の拡張 */ + /* 配列長を拡張する */ static int[] extend(int[] nums, int enlarge) { - // 拡張された長さの配列を初期化 + // 拡張後の長さを持つ配列を初期化する int[] res = new int[nums.length + enlarge]; - // 元の配列のすべての要素を新しい配列にコピー + // 元の配列の全要素を新しい配列にコピー for (int i = 0; i < nums.length; i++) { res[i] = nums[i]; } @@ -31,19 +31,19 @@ public class array { return res; } - /* `index` に要素 num を挿入 */ + /* 配列の index 番目に要素 num を挿入 */ static void insert(int[] nums, int num, int index) { - // `index` より後のすべての要素を1つ後ろに移動 + // インデックス index 以降の全要素を 1 つ後ろへ移動する for (int i = nums.length - 1; i > index; i--) { nums[i] = nums[i - 1]; } - // index の要素に num を代入 + // index の要素に num を代入する nums[index] = num; } - /* `index` の要素を削除 */ + /* index の要素を削除する */ static void remove(int[] nums, int index) { - // `index` より後のすべての要素を1つ前に移動 + // インデックス index より後ろの全要素を 1 つ前へ移動する for (int i = index; i < nums.length - 1; i++) { nums[i] = nums[i + 1]; } @@ -52,17 +52,17 @@ public class array { /* 配列を走査 */ static void traverse(int[] nums) { int count = 0; - // インデックスによる配列の走査 + // インデックスで配列を走査 for (int i = 0; i < nums.length; i++) { count += nums[i]; } - // 配列要素の走査 + // 配列要素を直接走査 for (int num : nums) { count += num; } } - /* 配列内で指定された要素を検索 */ + /* 配列内で指定要素を探す */ static int find(int[] nums, int target) { for (int i = 0; i < nums.length; i++) { if (nums[i] == target) @@ -71,7 +71,7 @@ public class array { return -1; } - /* ドライバーコード */ + /* Driver Code */ public static void main(String[] args) { /* 配列を初期化 */ int[] arr = new int[5]; @@ -81,25 +81,25 @@ public class array { /* ランダムアクセス */ int randomNum = randomAccess(nums); - System.out.println("nums からランダム要素を取得 = " + randomNum); + System.out.println("nums からランダムな要素を取得 " + randomNum); - /* 長さの拡張 */ + /* 長さを拡張 */ nums = extend(nums, 3); - System.out.println("配列の長さを8に拡張し、nums = " + Arrays.toString(nums)); + System.out.println("配列の長さを 8 に拡張し、nums = " + Arrays.toString(nums)); - /* 要素の挿入 */ + /* 要素を挿入する */ insert(nums, 6, 3); - System.out.println("インデックス3に数値6を挿入し、nums = " + Arrays.toString(nums)); + System.out.println("インデックス 3 に数値 6 を挿入し、nums = " + Arrays.toString(nums)); - /* 要素の削除 */ + /* 要素を削除 */ remove(nums, 2); - System.out.println("インデックス2の要素を削除し、nums = " + Arrays.toString(nums)); + System.out.println("インデックス 2 の要素を削除し、nums = " + Arrays.toString(nums)); - /* 配列の走査 */ + /* 配列を走査 */ traverse(nums); - /* 要素の検索 */ + /* 要素を探索する */ int index = find(nums, 3); - System.out.println("nums で要素3を見つけ、インデックス = " + index); + System.out.println("nums 内で要素 3 を検索し、インデックス = " + index); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_array_and_linkedlist/linked_list.java b/ja/codes/java/chapter_array_and_linkedlist/linked_list.java index 3ec773dec..21751712f 100644 --- a/ja/codes/java/chapter_array_and_linkedlist/linked_list.java +++ b/ja/codes/java/chapter_array_and_linkedlist/linked_list.java @@ -9,14 +9,14 @@ package chapter_array_and_linkedlist; import utils.*; public class linked_list { - /* 連結リストでノード n0 の後にノード P を挿入 */ + /* 連結リストでノード n0 の後ろにノード P を挿入する */ static void insert(ListNode n0, ListNode P) { ListNode n1 = n0.next; P.next = n1; n0.next = P; } - /* 連結リストでノード n0 の後の最初のノードを削除 */ + /* 連結リストでノード n0 の直後のノードを削除する */ static void remove(ListNode n0) { if (n0.next == null) return; @@ -26,7 +26,7 @@ public class linked_list { n0.next = n1; } - /* 連結リストの `index` のノードにアクセス */ + /* 連結リスト内で index 番目のノードにアクセス */ static ListNode access(ListNode head, int index) { for (int i = 0; i < index; i++) { if (head == null) @@ -36,7 +36,7 @@ public class linked_list { return head; } - /* 連結リストで値 target を持つ最初のノードを検索 */ + /* 連結リストで値が target の最初のノードを探す */ static int find(ListNode head, int target) { int index = 0; while (head != null) { @@ -48,39 +48,39 @@ public class linked_list { return -1; } - /* ドライバーコード */ + /* Driver Code */ public static void main(String[] args) { - /* 連結リストの初期化 */ + /* 連結リストを初期化 */ // 各ノードを初期化 ListNode n0 = new ListNode(1); ListNode n1 = new ListNode(3); ListNode n2 = new ListNode(2); ListNode n3 = new ListNode(5); ListNode n4 = new ListNode(4); - // ノード間の参照を構築 + // ノード間の参照を構築する n0.next = n1; n1.next = n2; n2.next = n3; n3.next = n4; - System.out.println("初期化された連結リストは"); + System.out.println("初期化した連結リストは"); PrintUtil.printLinkedList(n0); - /* ノードの挿入 */ + /* ノードを挿入 */ insert(n0, new ListNode(0)); System.out.println("ノード挿入後の連結リストは"); PrintUtil.printLinkedList(n0); - /* ノードの削除 */ + /* ノードを削除 */ remove(n0); System.out.println("ノード削除後の連結リストは"); PrintUtil.printLinkedList(n0); - /* ノードへのアクセス */ + /* ノードにアクセス */ ListNode node = access(n0, 3); - System.out.println("連結リストのインデックス3のノードの値 = " + node.val); + System.out.println("連結リストのインデックス 3 にあるノードの値 = " + node.val); - /* ノードの検索 */ + /* ノードを探索 */ int index = find(n0, 2); - System.out.println("連結リストで値2を持つノードのインデックス = " + index); + System.out.println("連結リスト内で値 2 のノードのインデックス = " + index); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_array_and_linkedlist/list.java b/ja/codes/java/chapter_array_and_linkedlist/list.java index e5459a4bc..a403cf51f 100644 --- a/ja/codes/java/chapter_array_and_linkedlist/list.java +++ b/ja/codes/java/chapter_array_and_linkedlist/list.java @@ -10,23 +10,23 @@ import java.util.*; public class list { public static void main(String[] args) { - /* リストの初期化 */ - // 配列の要素型は Integer[]、int のラッパークラス + /* リストを初期化 */ + // 配列の要素型は `int[]` のラッパークラスである `Integer[]` である点に注意 Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; List nums = new ArrayList<>(Arrays.asList(numbers)); System.out.println("リスト nums = " + nums); - /* 要素へのアクセス */ + /* 要素にアクセス */ int num = nums.get(1); - System.out.println("インデックス1の要素にアクセス、取得した num = " + num); + System.out.println("インデックス 1 の要素にアクセスし、num = " + num); - /* 要素の更新 */ + /* 要素を更新 */ nums.set(1, 0); - System.out.println("インデックス1の要素を0に更新し、nums = " + nums); + System.out.println("インデックス 1 の要素を 0 に更新し、nums = " + nums); - /* リストのクリア */ + /* リストを空にする */ nums.clear(); - System.out.println("リストをクリアした後、nums = " + nums); + System.out.println("リストを空にした後 nums = " + nums); /* 末尾に要素を追加 */ nums.add(1); @@ -34,33 +34,33 @@ public class list { nums.add(2); nums.add(5); nums.add(4); - System.out.println("要素を追加した後、nums = " + nums); + System.out.println("要素追加後 nums = " + nums); /* 中間に要素を挿入 */ nums.add(3, 6); - System.out.println("インデックス3に数値6を挿入し、nums = " + nums); + System.out.println("インデックス 3 に数値 6 を挿入し、nums = " + nums); - /* 要素の削除 */ + /* 要素を削除 */ nums.remove(3); - System.out.println("インデックス3の要素を削除し、nums = " + nums); + System.out.println("インデックス 3 の要素を削除し、nums = " + nums); - /* インデックスによるリストの走査 */ + /* インデックスでリストを走査 */ int count = 0; for (int i = 0; i < nums.size(); i++) { count += nums.get(i); } - /* リスト要素の走査 */ + /* リスト要素を直接走査 */ for (int x : nums) { count += x; } - /* 2つのリストの連結 */ + /* 2 つのリストを連結する */ List nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); nums.addAll(nums1); - System.out.println("リスト nums1 を nums に連結し、nums = " + nums); + System.out.println("リスト nums1 を nums の後ろに連結し、nums = " + nums); - /* リストのソート */ + /* リストをソート */ Collections.sort(nums); - System.out.println("リストをソートした後、nums = " + nums); + System.out.println("リストをソートした後 nums = " + nums); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_array_and_linkedlist/my_list.java b/ja/codes/java/chapter_array_and_linkedlist/my_list.java index 2d0a100e5..d1180f974 100644 --- a/ja/codes/java/chapter_array_and_linkedlist/my_list.java +++ b/ja/codes/java/chapter_array_and_linkedlist/my_list.java @@ -12,33 +12,33 @@ import java.util.*; class MyList { private int[] arr; // 配列(リスト要素を格納) private int capacity = 10; // リスト容量 - private int size = 0; // リスト長(現在の要素数) - private int extendRatio = 2; // リストの各拡張倍率 + private int size = 0; // リストの長さ(現在の要素数) + private int extendRatio = 2; // リスト拡張時の増加倍率 /* コンストラクタ */ public MyList() { arr = new int[capacity]; } - /* リスト長を取得(現在の要素数) */ + /* リストの長さを取得(現在の要素数) */ public int size() { return size; } - /* リスト容量を取得 */ + /* リスト容量を取得する */ public int capacity() { return capacity; } - /* 要素へのアクセス */ + /* 要素にアクセス */ public int get(int index) { - // インデックスが範囲外の場合、以下のように例外をスロー + // インデックスが範囲外なら例外を送出する。以下同様 if (index < 0 || index >= size) throw new IndexOutOfBoundsException("インデックスが範囲外です"); return arr[index]; } - /* 要素の更新 */ + /* 要素を更新 */ public void set(int index, int num) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException("インデックスが範囲外です"); @@ -47,7 +47,7 @@ class MyList { /* 末尾に要素を追加 */ public void add(int num) { - // 要素数が容量を超える場合、拡張メカニズムを実行 + // 要素数が容量を超えると、拡張機構が発動する if (size == capacity()) extendCapacity(); arr[size] = num; @@ -59,10 +59,10 @@ class MyList { public void insert(int index, int num) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException("インデックスが範囲外です"); - // 要素数が容量を超える場合、拡張メカニズムを実行 + // 要素数が容量を超えると、拡張機構が発動する if (size == capacity()) extendCapacity(); - // `index` より後のすべての要素を1つ後ろに移動 + // index 以降の要素をすべて 1 つ後ろへずらす for (int j = size - 1; j >= index; j--) { arr[j + 1] = arr[j]; } @@ -71,12 +71,12 @@ class MyList { size++; } - /* 要素の削除 */ + /* 要素を削除 */ public int remove(int index) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException("インデックスが範囲外です"); int num = arr[index]; - // `index` より後のすべての要素を1つ前に移動 + // インデックス index より後の要素をすべて 1 つ前に移動する for (int j = index; j < size - 1; j++) { arr[j] = arr[j + 1]; } @@ -86,18 +86,18 @@ class MyList { return num; } - /* リストを拡張 */ + /* リストの拡張 */ public void extendCapacity() { - // 元の配列の長さを extendRatio 倍した新しい配列を作成し、元の配列を新しい配列にコピー + // 元の配列の extendRatio 倍の長さを持つ新しい配列を作成し、元の配列をコピーする arr = Arrays.copyOf(arr, capacity() * extendRatio); - // リスト容量を更新 + // リストの容量を更新 capacity = arr.length; } - /* リストを配列に変換 */ + /* リストを配列に変換する */ public int[] toArray() { int size = size(); - // 有効な長さ範囲内の要素のみを変換 + // 有効長の範囲内のリスト要素のみを変換 int[] arr = new int[size]; for (int i = 0; i < size; i++) { arr[i] = get(i); @@ -107,9 +107,9 @@ class MyList { } public class my_list { - /* ドライバーコード */ + /* Driver Code */ public static void main(String[] args) { - /* リストの初期化 */ + /* リストを初期化 */ MyList nums = new MyList(); /* 末尾に要素を追加 */ nums.add(1); @@ -118,30 +118,30 @@ public class my_list { nums.add(5); nums.add(4); System.out.println("リスト nums = " + Arrays.toString(nums.toArray()) + - ", 容量 = " + nums.capacity() + ", 長さ = " + nums.size()); + " 、容量 = " + nums.capacity() + " 、長さ = " + nums.size()); /* 中間に要素を挿入 */ nums.insert(3, 6); - System.out.println("インデックス3に数値6を挿入し、nums = " + Arrays.toString(nums.toArray())); + System.out.println("インデックス 3 に数値 6 を挿入すると、nums = " + Arrays.toString(nums.toArray())); - /* 要素の削除 */ + /* 要素を削除 */ nums.remove(3); - System.out.println("インデックス3の要素を削除し、nums = " + Arrays.toString(nums.toArray())); + System.out.println("インデックス 3 の要素を削除すると、nums = " + Arrays.toString(nums.toArray())); - /* 要素へのアクセス */ + /* 要素にアクセス */ int num = nums.get(1); - System.out.println("インデックス1の要素にアクセス、取得した num = " + num); + System.out.println("インデックス 1 の要素にアクセスし、num = " + num); - /* 要素の更新 */ + /* 要素を更新 */ nums.set(1, 0); - System.out.println("インデックス1の要素を0に更新し、nums = " + Arrays.toString(nums.toArray())); + System.out.println("インデックス 1 の要素を 0 に更新すると、nums = " + Arrays.toString(nums.toArray())); - /* 拡張メカニズムのテスト */ + /* 拡張機構をテストする */ for (int i = 0; i < 10; i++) { - // i = 5 の時、リスト長がリスト容量を超え、この時点で拡張メカニズムが実行される + // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する nums.add(i); } - System.out.println("拡張後、リスト nums = " + Arrays.toString(nums.toArray()) + - ", 容量 = " + nums.capacity() + ", 長さ = " + nums.size()); + System.out.println("拡張後のリスト nums = " + Arrays.toString(nums.toArray()) + + " 、容量 = " + nums.capacity() + " 、長さ = " + nums.size()); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_backtracking/n_queens.java b/ja/codes/java/chapter_backtracking/n_queens.java index 1f793a4f2..6590ddde0 100644 --- a/ja/codes/java/chapter_backtracking/n_queens.java +++ b/ja/codes/java/chapter_backtracking/n_queens.java @@ -9,10 +9,10 @@ package chapter_backtracking; import java.util.*; public class n_queens { - /* バックトラッキングアルゴリズム:n クイーン */ + /* バックトラッキング:N クイーン */ public static void backtrack(int row, int n, List> state, List>> res, boolean[] cols, boolean[] diags1, boolean[] diags2) { - // すべての行が配置されたら、解を記録 + // すべての行への配置が完了したら、解を記録する if (row == n) { List> copyState = new ArrayList<>(); for (List sRow : state) { @@ -23,26 +23,26 @@ public class n_queens { } // すべての列を走査 for (int col = 0; col < n; col++) { - // セルに対応する主対角線と副対角線を計算 + // このマスに対応する主対角線と副対角線を計算 int diag1 = row - col + n - 1; int diag2 = row + col; - // 剪定:セルの列、主対角線、副対角線にクイーンを配置することを許可しない + // 枝刈り:そのマスの列、主対角線、副対角線にクイーンがあってはならない if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { - // 試行:セルにクイーンを配置 + // 試行:そのマスにクイーンを置く state.get(row).set(col, "Q"); cols[col] = diags1[diag1] = diags2[diag2] = true; - // 次の行を配置 + // 次の行に配置する backtrack(row + 1, n, state, res, cols, diags1, diags2); - // 回退:セルを空のスポットに復元 + // 戻す:そのマスを空きマスに戻す state.get(row).set(col, "#"); cols[col] = diags1[diag1] = diags2[diag2] = false; } } } - /* n クイーンを解く */ + /* N クイーンを解く */ public static List>> nQueens(int n) { - // n*n サイズのチェスボードを初期化、'Q' はクイーンを表し、'#' は空のスポットを表す + // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す List> state = new ArrayList<>(); for (int i = 0; i < n; i++) { List row = new ArrayList<>(); @@ -51,9 +51,9 @@ public class n_queens { } state.add(row); } - boolean[] cols = new boolean[n]; // クイーンのある列を記録 - boolean[] diags1 = new boolean[2 * n - 1]; // クイーンのある主対角線を記録 - boolean[] diags2 = new boolean[2 * n - 1]; // クイーンのある副対角線を記録 + boolean[] cols = new boolean[n]; // 列にクイーンがあるか記録 + boolean[] diags1 = new boolean[2 * n - 1]; // 主対角線にクイーンがあるかを記録 + boolean[] diags2 = new boolean[2 * n - 1]; // 副対角線にクイーンがあるかを記録 List>> res = new ArrayList<>(); backtrack(0, n, state, res, cols, diags1, diags2); @@ -65,8 +65,8 @@ public class n_queens { int n = 4; List>> res = nQueens(n); - System.out.println("チェスボードの次元を " + n + " として入力"); - System.out.println("クイーン配置解の総数 = " + res.size()); + System.out.println("盤面の縦横サイズは " + n); + System.out.println("クイーンの配置方法は全部で " + res.size() + " 通り"); for (List> state : res) { System.out.println("--------------------"); for (List row : state) { @@ -74,4 +74,4 @@ public class n_queens { } } } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_backtracking/permutations_i.java b/ja/codes/java/chapter_backtracking/permutations_i.java index 3cb122cb8..f6641392d 100644 --- a/ja/codes/java/chapter_backtracking/permutations_i.java +++ b/ja/codes/java/chapter_backtracking/permutations_i.java @@ -9,9 +9,9 @@ package chapter_backtracking; import java.util.*; public class permutations_i { - /* バックトラッキングアルゴリズム:順列 I */ + /* バックトラッキング:順列 I */ public static void backtrack(List state, int[] choices, boolean[] selected, List> res) { - // 状態の長さが要素数と等しくなったら、解を記録 + // 状態の長さが要素数に等しければ、解を記録 if (state.size() == choices.length) { res.add(new ArrayList(state)); return; @@ -19,21 +19,21 @@ public class permutations_i { // すべての選択肢を走査 for (int i = 0; i < choices.length; i++) { int choice = choices[i]; - // 剪定:要素の重複選択を許可しない + // 枝刈り:要素の重複選択を許可しない if (!selected[i]) { - // 試行:選択を行い、状態を更新 + // 試行: 選択を行い、状態を更新 selected[i] = true; state.add(choice); - // 次のラウンドの選択に進む + // 次の選択へ進む backtrack(state, choices, selected, res); - // 回退:選択を取り消し、前の状態に復元 + // バックトラック:選択を取り消し、前の状態に戻す selected[i] = false; state.remove(state.size() - 1); } } } - /* 順列 I */ + /* 全順列 I */ static List> permutationsI(int[] nums) { List> res = new ArrayList>(); backtrack(new ArrayList(), nums, new boolean[nums.length], res); @@ -48,4 +48,4 @@ public class permutations_i { System.out.println("入力配列 nums = " + Arrays.toString(nums)); System.out.println("すべての順列 res = " + res); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_backtracking/permutations_ii.java b/ja/codes/java/chapter_backtracking/permutations_ii.java index c36e0f33e..251c61b08 100644 --- a/ja/codes/java/chapter_backtracking/permutations_ii.java +++ b/ja/codes/java/chapter_backtracking/permutations_ii.java @@ -9,9 +9,9 @@ package chapter_backtracking; import java.util.*; public class permutations_ii { - /* バックトラッキングアルゴリズム:順列 II */ + /* バックトラッキング:順列 II */ static void backtrack(List state, int[] choices, boolean[] selected, List> res) { - // 状態の長さが要素数と等しくなったら、解を記録 + // 状態の長さが要素数に等しければ、解を記録 if (state.size() == choices.length) { res.add(new ArrayList(state)); return; @@ -20,22 +20,22 @@ public class permutations_ii { Set duplicated = new HashSet(); for (int i = 0; i < choices.length; i++) { int choice = choices[i]; - // 剪定:要素の重複選択を許可せず、等しい要素の重複選択も許可しない + // 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない if (!selected[i] && !duplicated.contains(choice)) { - // 試行:選択を行い、状態を更新 - duplicated.add(choice); // 選択された要素値を記録 + // 試行: 選択を行い、状態を更新 + duplicated.add(choice); // 選択済みの要素値を記録 selected[i] = true; state.add(choice); - // 次のラウンドの選択に進む + // 次の選択へ進む backtrack(state, choices, selected, res); - // 回退:選択を取り消し、前の状態に復元 + // バックトラック:選択を取り消し、前の状態に戻す selected[i] = false; state.remove(state.size() - 1); } } } - /* 順列 II */ + /* 全順列 II */ static List> permutationsII(int[] nums) { List> res = new ArrayList>(); backtrack(new ArrayList(), nums, new boolean[nums.length], res); @@ -50,4 +50,4 @@ public class permutations_ii { System.out.println("入力配列 nums = " + Arrays.toString(nums)); System.out.println("すべての順列 res = " + res); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_backtracking/preorder_traversal_i_compact.java b/ja/codes/java/chapter_backtracking/preorder_traversal_i_compact.java index e85e66951..3c5c536d6 100644 --- a/ja/codes/java/chapter_backtracking/preorder_traversal_i_compact.java +++ b/ja/codes/java/chapter_backtracking/preorder_traversal_i_compact.java @@ -12,7 +12,7 @@ import java.util.*; public class preorder_traversal_i_compact { static List res; - /* 前順走査:例1 */ + /* 前順走査:例題 1 */ static void preOrder(TreeNode root) { if (root == null) { return; @@ -30,15 +30,15 @@ public class preorder_traversal_i_compact { System.out.println("\n二分木を初期化"); PrintUtil.printTree(root); - // 前順走査 + // 先行順走査 res = new ArrayList<>(); preOrder(root); - System.out.println("\n値7のノードをすべて出力"); + System.out.println("\n値が 7 のノードをすべて出力"); List vals = new ArrayList<>(); for (TreeNode node : res) { vals.add(node.val); } System.out.println(vals); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java b/ja/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java index b65dc42fb..802446081 100644 --- a/ja/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java +++ b/ja/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java @@ -13,12 +13,12 @@ public class preorder_traversal_ii_compact { static List path; static List> res; - /* 前順走査:例2 */ + /* 前順走査:例題 2 */ static void preOrder(TreeNode root) { if (root == null) { return; } - // 試行 + // 試す path.add(root); if (root.val == 7) { // 解を記録 @@ -26,7 +26,7 @@ public class preorder_traversal_ii_compact { } preOrder(root.left); preOrder(root.right); - // 回退 + // バックトラック path.remove(path.size() - 1); } @@ -35,12 +35,12 @@ public class preorder_traversal_ii_compact { System.out.println("\n二分木を初期化"); PrintUtil.printTree(root); - // 前順走査 + // 先行順走査 path = new ArrayList<>(); res = new ArrayList<>(); preOrder(root); - System.out.println("\nルートからノード7までのすべてのパスを出力"); + System.out.println("\n根ノードからノード 7 までのすべての経路を出力"); for (List path : res) { List vals = new ArrayList<>(); for (TreeNode node : path) { @@ -49,4 +49,4 @@ public class preorder_traversal_ii_compact { System.out.println(vals); } } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java b/ja/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java index ad60d7a70..dc0f4241b 100644 --- a/ja/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java +++ b/ja/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java @@ -13,13 +13,13 @@ public class preorder_traversal_iii_compact { static List path; static List> res; - /* 前順走査:例3 */ + /* 前順走査:例題 3 */ static void preOrder(TreeNode root) { - // 剪定 + // 枝刈り if (root == null || root.val == 3) { return; } - // 試行 + // 試す path.add(root); if (root.val == 7) { // 解を記録 @@ -27,7 +27,7 @@ public class preorder_traversal_iii_compact { } preOrder(root.left); preOrder(root.right); - // 回退 + // バックトラック path.remove(path.size() - 1); } @@ -36,12 +36,12 @@ public class preorder_traversal_iii_compact { System.out.println("\n二分木を初期化"); PrintUtil.printTree(root); - // 前順走査 + // 先行順走査 path = new ArrayList<>(); res = new ArrayList<>(); preOrder(root); - System.out.println("\nルートからノード7までのすべてのパスを出力、値3のノードは含まない"); + System.out.println("\n根ノードからノード 7 までのすべての経路を出力し、経路には値が 3 のノードを含めない"); for (List path : res) { List vals = new ArrayList<>(); for (TreeNode node : path) { @@ -50,4 +50,4 @@ public class preorder_traversal_iii_compact { System.out.println(vals); } } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_backtracking/preorder_traversal_iii_template.java b/ja/codes/java/chapter_backtracking/preorder_traversal_iii_template.java index b0e396550..73bff1585 100644 --- a/ja/codes/java/chapter_backtracking/preorder_traversal_iii_template.java +++ b/ja/codes/java/chapter_backtracking/preorder_traversal_iii_template.java @@ -20,7 +20,7 @@ public class preorder_traversal_iii_template { res.add(new ArrayList<>(state)); } - /* 現在の状態下で選択が合法かどうかを判定 */ + /* 現在の状態で、この選択が有効かどうかを判定 */ static boolean isValid(List state, TreeNode choice) { return choice != null && choice.val != 3; } @@ -30,27 +30,27 @@ public class preorder_traversal_iii_template { state.add(choice); } - /* 状態を復元 */ + /* 状態を元に戻す */ static void undoChoice(List state, TreeNode choice) { state.remove(state.size() - 1); } - /* バックトラッキングアルゴリズム:例3 */ + /* バックトラッキング:例題 3 */ static void backtrack(List state, List choices, List> res) { - // 解かどうかをチェック + // 解かどうかを確認 if (isSolution(state)) { // 解を記録 recordSolution(state, res); } // すべての選択肢を走査 for (TreeNode choice : choices) { - // 剪定:選択が合法かどうかをチェック + // 枝刈り:選択が妥当かを確認する if (isValid(state, choice)) { - // 試行:選択を行い、状態を更新 + // 試行: 選択を行い、状態を更新 makeChoice(state, choice); - // 次のラウンドの選択に進む + // 次の選択へ進む backtrack(state, Arrays.asList(choice.left, choice.right), res); - // 回退:選択を取り消し、前の状態に復元 + // バックトラック:選択を取り消し、前の状態に戻す undoChoice(state, choice); } } @@ -61,11 +61,11 @@ public class preorder_traversal_iii_template { System.out.println("\n二分木を初期化"); PrintUtil.printTree(root); - // バックトラッキングアルゴリズム + // バックトラッキング法 List> res = new ArrayList<>(); backtrack(new ArrayList<>(), Arrays.asList(root), res); - System.out.println("\nルートからノード7までのすべてのパスを出力、パスには値3のノードを含まないことが要求される"); + System.out.println("\n根ノードからノード 7 までのすべての経路を出力し、経路に値が 3 のノードを含まないことを条件とする"); for (List path : res) { List vals = new ArrayList<>(); for (TreeNode node : path) { @@ -74,4 +74,4 @@ public class preorder_traversal_iii_template { System.out.println(vals); } } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_backtracking/subset_sum_i.java b/ja/codes/java/chapter_backtracking/subset_sum_i.java index fe863417c..5ed5ce94f 100644 --- a/ja/codes/java/chapter_backtracking/subset_sum_i.java +++ b/ja/codes/java/chapter_backtracking/subset_sum_i.java @@ -9,36 +9,36 @@ package chapter_backtracking; import java.util.*; public class subset_sum_i { - /* バックトラッキングアルゴリズム:部分集合和 I */ + /* バックトラッキング:部分和 I */ static void backtrack(List state, int target, int[] choices, int start, List> res) { - // 部分集合の和がtargetと等しいとき、解を記録 + // 部分集合の和が target に等しければ、解を記録 if (target == 0) { res.add(new ArrayList<>(state)); return; } // すべての選択肢を走査 - // 剪定二:startから走査を開始し、重複する部分集合の生成を回避 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける for (int i = start; i < choices.length; i++) { - // 剪定一:部分集合の和がtargetを超えた場合、即座にループを終了 - // 配列がソートされているため、後の要素はさらに大きく、部分集合の和は必ずtargetを超える + // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if (target - choices[i] < 0) { break; } - // 試行:選択を行い、target、startを更新 + // 試す:選択を行い、target と start を更新 state.add(choices[i]); - // 次のラウンドの選択に進む + // 次の選択へ進む backtrack(state, target - choices[i], choices, i, res); - // 回退:選択を取り消し、前の状態に復元 + // バックトラック:選択を取り消し、前の状態に戻す state.remove(state.size() - 1); } } - /* 部分集合和 I を解く */ + /* 部分和 I を解く */ static List> subsetSumI(int[] nums, int target) { List state = new ArrayList<>(); // 状態(部分集合) Arrays.sort(nums); // nums をソート - int start = 0; // 走査の開始点 - List> res = new ArrayList<>(); // 結果リスト(部分集合リスト) + int start = 0; // 開始点を走査 + List> res = new ArrayList<>(); // 結果リスト(部分集合のリスト) backtrack(state, target, nums, start, res); return res; } @@ -50,6 +50,6 @@ public class subset_sum_i { List> res = subsetSumI(nums, target); System.out.println("入力配列 nums = " + Arrays.toString(nums) + ", target = " + target); - System.out.println("和が " + target + " のすべての部分集合 res = " + res); + System.out.println("和が " + target + " に等しいすべての部分集合 res = " + res); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_backtracking/subset_sum_i_naive.java b/ja/codes/java/chapter_backtracking/subset_sum_i_naive.java index 19cd8d2dd..1421e4d7c 100644 --- a/ja/codes/java/chapter_backtracking/subset_sum_i_naive.java +++ b/ja/codes/java/chapter_backtracking/subset_sum_i_naive.java @@ -9,33 +9,33 @@ package chapter_backtracking; import java.util.*; public class subset_sum_i_naive { - /* バックトラッキングアルゴリズム:部分集合和 I */ + /* バックトラッキング:部分和 I */ static void backtrack(List state, int target, int total, int[] choices, List> res) { - // 部分集合の和がtargetと等しいとき、解を記録 + // 部分集合の和が target に等しければ、解を記録 if (total == target) { res.add(new ArrayList<>(state)); return; } // すべての選択肢を走査 for (int i = 0; i < choices.length; i++) { - // 剪定:部分集合の和がtargetを超えた場合、その選択をスキップ + // 枝刈り:部分和が target を超える場合はその選択をスキップする if (total + choices[i] > target) { continue; } - // 試行:選択を行い、要素とtotalを更新 + // 試行:選択を行い、要素と total を更新する state.add(choices[i]); - // 次のラウンドの選択に進む + // 次の選択へ進む backtrack(state, target, total + choices[i], choices, res); - // 回退:選択を取り消し、前の状態に復元 + // バックトラック:選択を取り消し、前の状態に戻す state.remove(state.size() - 1); } } - /* 部分集合和 I を解く(重複する部分集合を含む) */ + /* 部分和 I を解く(重複部分集合を含む) */ static List> subsetSumINaive(int[] nums, int target) { List state = new ArrayList<>(); // 状態(部分集合) - int total = 0; // 部分集合の和 - List> res = new ArrayList<>(); // 結果リスト(部分集合リスト) + int total = 0; // 部分和 + List> res = new ArrayList<>(); // 結果リスト(部分集合のリスト) backtrack(state, target, total, nums, res); return res; } @@ -47,7 +47,7 @@ public class subset_sum_i_naive { List> res = subsetSumINaive(nums, target); System.out.println("入力配列 nums = " + Arrays.toString(nums) + ", target = " + target); - System.out.println("和が " + target + " のすべての部分集合 res = " + res); - System.out.println("この方法の結果には重複する集合が含まれています"); + System.out.println("和が " + target + " に等しいすべての部分集合 res = " + res); + System.out.println("注意: この方法の出力結果には重複した集合が含まれます"); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_backtracking/subset_sum_ii.java b/ja/codes/java/chapter_backtracking/subset_sum_ii.java index dbfbde052..4ff75f982 100644 --- a/ja/codes/java/chapter_backtracking/subset_sum_ii.java +++ b/ja/codes/java/chapter_backtracking/subset_sum_ii.java @@ -9,41 +9,41 @@ package chapter_backtracking; import java.util.*; public class subset_sum_ii { - /* バックトラッキングアルゴリズム:部分集合和 II */ + /* バックトラッキング:部分和 II */ static void backtrack(List state, int target, int[] choices, int start, List> res) { - // 部分集合の和がtargetと等しいとき、解を記録 + // 部分集合の和が target に等しければ、解を記録 if (target == 0) { res.add(new ArrayList<>(state)); return; } // すべての選択肢を走査 - // 剪定二:startから走査を開始し、重複する部分集合の生成を回避 - // 剪定三:startから走査を開始し、同じ要素の繰り返し選択を回避 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける for (int i = start; i < choices.length; i++) { - // 剪定一:部分集合の和がtargetを超えた場合、即座にループを終了 - // 配列がソートされているため、後の要素はさらに大きく、部分集合の和は必ずtargetを超える + // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if (target - choices[i] < 0) { break; } - // 剪定四:要素が左の要素と等しい場合、検索ブランチの重複を示すのでスキップ + // 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする if (i > start && choices[i] == choices[i - 1]) { continue; } - // 試行:選択を行い、target、startを更新 + // 試す:選択を行い、target と start を更新 state.add(choices[i]); - // 次のラウンドの選択に進む + // 次の選択へ進む backtrack(state, target - choices[i], choices, i + 1, res); - // 回退:選択を取り消し、前の状態に復元 + // バックトラック:選択を取り消し、前の状態に戻す state.remove(state.size() - 1); } } - /* 部分集合和 II を解く */ + /* 部分和 II を解く */ static List> subsetSumII(int[] nums, int target) { List state = new ArrayList<>(); // 状態(部分集合) Arrays.sort(nums); // nums をソート - int start = 0; // 走査の開始点 - List> res = new ArrayList<>(); // 結果リスト(部分集合リスト) + int start = 0; // 開始点を走査 + List> res = new ArrayList<>(); // 結果リスト(部分集合のリスト) backtrack(state, target, nums, start, res); return res; } @@ -55,6 +55,6 @@ public class subset_sum_ii { List> res = subsetSumII(nums, target); System.out.println("入力配列 nums = " + Arrays.toString(nums) + ", target = " + target); - System.out.println("和が " + target + " のすべての部分集合 res = " + res); + System.out.println("和が " + target + " に等しいすべての部分集合 res = " + res); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_computational_complexity/iteration.java b/ja/codes/java/chapter_computational_complexity/iteration.java index bc5e3fc83..269de876e 100644 --- a/ja/codes/java/chapter_computational_complexity/iteration.java +++ b/ja/codes/java/chapter_computational_complexity/iteration.java @@ -10,7 +10,7 @@ public class iteration { /* for ループ */ static int forLoop(int n) { int res = 0; - // 1, 2, ..., n-1, n の合計をループ計算 + // 1, 2, ..., n-1, n を順に加算する for (int i = 1; i <= n; i++) { res += i; } @@ -20,35 +20,35 @@ public class iteration { /* while ループ */ static int whileLoop(int n) { int res = 0; - int i = 1; // 条件変数を初期化 - // 1, 2, ..., n-1, n の合計をループ計算 + int i = 1; // 条件変数を初期化する + // 1, 2, ..., n-1, n を順に加算する while (i <= n) { res += i; - i++; // 条件変数を更新 + i++; // 条件変数を更新する } return res; } - /* while ループ(2つの更新) */ + /* while ループ(2回更新) */ static int whileLoopII(int n) { int res = 0; - int i = 1; // 条件変数を初期化 - // 1, 4, 10, ... の合計をループ計算 + int i = 1; // 条件変数を初期化する + // 1, 4, 10, ... を順に加算する while (i <= n) { res += i; - // 条件変数を更新 + // 条件変数を更新する i++; i *= 2; } return res; } - /* 2重 for ループ */ + /* 二重 for ループ */ static String nestedForLoop(int n) { StringBuilder res = new StringBuilder(); - // ループ i = 1, 2, ..., n-1, n + // i = 1, 2, ..., n-1, n とループする for (int i = 1; i <= n; i++) { - // ループ j = 1, 2, ..., n-1, n + // j = 1, 2, ..., n-1, n とループする for (int j = 1; j <= n; j++) { res.append("(" + i + ", " + j + "), "); } @@ -56,7 +56,7 @@ public class iteration { return res.toString(); } - /* ドライバーコード */ + /* Driver Code */ public static void main(String[] args) { int n = 5; int res; @@ -68,9 +68,9 @@ public class iteration { System.out.println("\nwhile ループの合計結果 res = " + res); res = whileLoopII(n); - System.out.println("\nwhile ループ(2つの更新)の合計結果 res = " + res); + System.out.println("\nwhile ループ(2 回更新)の合計結果 res = " + res); String resStr = nestedForLoop(n); - System.out.println("\n2重 for ループ走査の結果 = " + resStr); + System.out.println("\n二重 for ループの走査結果 " + resStr); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_computational_complexity/recursion.java b/ja/codes/java/chapter_computational_complexity/recursion.java index b1d79aa86..1cd12a3f6 100644 --- a/ja/codes/java/chapter_computational_complexity/recursion.java +++ b/ja/codes/java/chapter_computational_complexity/recursion.java @@ -16,23 +16,23 @@ public class recursion { return 1; // 再帰:再帰呼び出し int res = recur(n - 1); - // 戻り値:結果を返す + // 帰りがけ:結果を返す return n + res; } - /* 反復で再帰をシミュレート */ + /* 反復で再帰を模擬する */ static int forLoopRecur(int n) { - // 明示的なスタックを使用してシステムコールスタックをシミュレート + // 明示的なスタックを使ってシステムコールスタックを模擬する Stack stack = new Stack<>(); int res = 0; // 再帰:再帰呼び出し for (int i = n; i > 0; i--) { - // 「スタックへのプッシュ」で「再帰」をシミュレート + // 「スタックへのプッシュ」で「再帰」を模擬する stack.push(i); } - // 戻り値:結果を返す + // 帰りがけ:結果を返す while (!stack.isEmpty()) { - // 「スタックからのポップ」で「戻り値」をシミュレート + // 「スタックから取り出す操作」で「帰り」をシミュレート res += stack.pop(); } // res = 1+2+3+...+n @@ -53,13 +53,13 @@ public class recursion { // 終了条件 f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; - // 再帰呼び出し f(n) = f(n-1) + f(n-2) + // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す int res = fib(n - 1) + fib(n - 2); // 結果 f(n) を返す return res; } - /* ドライバーコード */ + /* Driver Code */ public static void main(String[] args) { int n = 5; int res; @@ -68,12 +68,12 @@ public class recursion { System.out.println("\n再帰関数の合計結果 res = " + res); res = forLoopRecur(n); - System.out.println("\n反復を使用して再帰をシミュレートした合計結果 res = " + res); + System.out.println("\n反復による再帰シミュレーションの合計結果 res = " + res); res = tailRecur(n, 0); System.out.println("\n末尾再帰関数の合計結果 res = " + res); res = fib(n); - System.out.println("\nフィボナッチ数列の第 " + n + " 番目の数は " + res); + System.out.println("\nフィボナッチ数列の第 " + n + " 項は " + res); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_computational_complexity/space_complexity.java b/ja/codes/java/chapter_computational_complexity/space_complexity.java index 6db3f2613..993d66963 100644 --- a/ja/codes/java/chapter_computational_complexity/space_complexity.java +++ b/ja/codes/java/chapter_computational_complexity/space_complexity.java @@ -12,44 +12,44 @@ import java.util.*; public class space_complexity { /* 関数 */ static int function() { - // 何らかの操作を実行 + // 何らかの処理を行う return 0; } - /* 定数計算量 */ + /* 定数階 */ static void constant(int n) { - // 定数、変数、オブジェクトは O(1) 空間を占める + // 定数、変数、オブジェクトは O(1) の空間を占める final int a = 0; int b = 0; int[] nums = new int[10000]; ListNode node = new ListNode(0); - // ループ内の変数は O(1) 空間を占める + // ループ内の変数は O(1) の空間を占める for (int i = 0; i < n; i++) { int c = 0; } - // ループ内の関数は O(1) 空間を占める + // ループ内の関数は O(1) の空間を占める for (int i = 0; i < n; i++) { function(); } } - /* 線形計算量 */ + /* 線形階 */ static void linear(int n) { - // 長さ n の配列は O(n) 空間を占める + // 長さ n の配列は O(n) の空間を使用 int[] nums = new int[n]; - // 長さ n のリストは O(n) 空間を占める + // 長さ n のリストは O(n) の空間を使用 List nodes = new ArrayList<>(); for (int i = 0; i < n; i++) { nodes.add(new ListNode(i)); } - // 長さ n のハッシュテーブルは O(n) 空間を占める + // 長さ n のハッシュテーブルは O(n) の空間を使用 Map map = new HashMap<>(); for (int i = 0; i < n; i++) { map.put(i, String.valueOf(i)); } } - /* 線形計算量(再帰実装) */ + /* 線形時間(再帰実装) */ static void linearRecur(int n) { System.out.println("再帰 n = " + n); if (n == 1) @@ -57,11 +57,11 @@ public class space_complexity { linearRecur(n - 1); } - /* 二次計算量 */ + /* 二乗階 */ static void quadratic(int n) { - // 行列は O(n^2) 空間を占める + // 行列は O(n^2) の空間を使用する int[][] numMatrix = new int[n][n]; - // 二次元リストは O(n^2) 空間を占める + // 二次元リストは O(n^2) の空間を使用 List> numList = new ArrayList<>(); for (int i = 0; i < n; i++) { List tmp = new ArrayList<>(); @@ -72,17 +72,17 @@ public class space_complexity { } } - /* 二次計算量(再帰実装) */ + /* 二次時間(再帰実装) */ static int quadraticRecur(int n) { if (n <= 0) return 0; - // 配列 nums の長さ = n, n-1, ..., 2, 1 + // 配列 nums の長さは n, n-1, ..., 2, 1 int[] nums = new int[n]; - System.out.println("再帰 n = " + n + " の nums の長さ = " + nums.length); + System.out.println("再帰 n = " + n + " における nums の長さ = " + nums.length); return quadraticRecur(n - 1); } - /* 指数計算量(完全二分木の構築) */ + /* 指数時間(完全二分木の構築) */ static TreeNode buildTree(int n) { if (n == 0) return null; @@ -92,19 +92,19 @@ public class space_complexity { return root; } - /* ドライバーコード */ + /* Driver Code */ public static void main(String[] args) { int n = 5; - // 定数計算量 + // 定数階 constant(n); - // 線形計算量 + // 線形階 linear(n); linearRecur(n); - // 二次計算量 + // 二乗階 quadratic(n); quadraticRecur(n); - // 指数計算量 + // 指数オーダー TreeNode root = buildTree(n); PrintUtil.printTree(root); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_computational_complexity/time_complexity.java b/ja/codes/java/chapter_computational_complexity/time_complexity.java index aaac409cb..662430462 100644 --- a/ja/codes/java/chapter_computational_complexity/time_complexity.java +++ b/ja/codes/java/chapter_computational_complexity/time_complexity.java @@ -7,7 +7,7 @@ package chapter_computational_complexity; public class time_complexity { - /* 定数計算量 */ + /* 定数階 */ static int constant(int n) { int count = 0; int size = 100000; @@ -16,7 +16,7 @@ public class time_complexity { return count; } - /* 線形計算量 */ + /* 線形階 */ static int linear(int n) { int count = 0; for (int i = 0; i < n; i++) @@ -24,20 +24,20 @@ public class time_complexity { return count; } - /* 線形計算量(配列の走査) */ + /* 線形時間(配列を走査) */ static int arrayTraversal(int[] nums) { int count = 0; - // ループ回数は配列の長さに比例 + // ループ回数は配列長に比例する for (int num : nums) { count++; } return count; } - /* 二次計算量 */ + /* 二乗階 */ static int quadratic(int n) { int count = 0; - // ループ回数はデータサイズ n の二乗に比例 + // ループ回数はデータサイズ n の二乗に比例する for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; @@ -46,29 +46,29 @@ public class time_complexity { return count; } - /* 二次計算量(バブルソート) */ + /* 二次時間(バブルソート) */ static int bubbleSort(int[] nums) { - int count = 0; // カウンター - // 外側ループ:未ソート範囲は [0, i] + int count = 0; // カウンタ + // 外側のループ:未ソート区間は [0, i] for (int i = nums.length - 1; i > 0; i--) { - // 内側ループ:未ソート範囲 [0, i] の最大要素を範囲の右端にスワップ + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { - // nums[j] と nums[j + 1] をスワップ + // nums[j] と nums[j + 1] を交換 int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; - count += 3; // 要素のスワップには3つの個別操作が含まれる + count += 3; // 要素交換には 3 回の単位操作が含まれる } } } return count; } - /* 指数計算量(ループ実装) */ + /* 指数時間(ループ実装) */ static int exponential(int n) { int count = 0, base = 1; - // セルは毎ラウンド2つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成 + // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する for (int i = 0; i < n; i++) { for (int j = 0; j < base; j++) { count++; @@ -79,14 +79,14 @@ public class time_complexity { return count; } - /* 指数計算量(再帰実装) */ + /* 指数時間(再帰実装) */ static int expRecur(int n) { if (n == 1) return 1; return expRecur(n - 1) + expRecur(n - 1) + 1; } - /* 対数計算量(ループ実装) */ + /* 対数時間(ループ実装) */ static int logarithmic(int n) { int count = 0; while (n > 1) { @@ -96,14 +96,14 @@ public class time_complexity { return count; } - /* 対数計算量(再帰実装) */ + /* 対数時間(再帰実装) */ static int logRecur(int n) { if (n <= 1) return 0; return logRecur(n / 2) + 1; } - /* 線形対数計算量 */ + /* 線形対数時間 */ static int linearLogRecur(int n) { if (n <= 1) return 1; @@ -114,54 +114,54 @@ public class time_complexity { return count; } - /* 階乗計算量(再帰実装) */ + /* 階乗時間(再帰実装) */ static int factorialRecur(int n) { if (n == 0) return 1; int count = 0; - // 1から n に分裂 + // 1個から n 個に分裂 for (int i = 0; i < n; i++) { count += factorialRecur(n - 1); } return count; } - /* ドライバーコード */ + /* Driver Code */ public static void main(String[] args) { - // n を変更して、さまざまな計算量での操作回数の変化傾向を体験可能 + // n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる int n = 8; System.out.println("入力データサイズ n = " + n); int count = constant(n); - System.out.println("定数計算量の操作回数 = " + count); + System.out.println("定数時間の操作回数 = " + count); count = linear(n); - System.out.println("線形計算量の操作回数 = " + count); + System.out.println("線形時間の操作回数 = " + count); count = arrayTraversal(new int[n]); - System.out.println("線形計算量の操作回数(配列走査) = " + count); + System.out.println("線形時間(配列走査)の操作回数 = " + count); count = quadratic(n); - System.out.println("二次計算量の操作回数 = " + count); + System.out.println("2 次時間の操作回数 = " + count); int[] nums = new int[n]; for (int i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] count = bubbleSort(nums); - System.out.println("二次計算量の操作回数(バブルソート) = " + count); + System.out.println("2 次時間(バブルソート)の操作回数 = " + count); count = exponential(n); - System.out.println("指数計算量の操作回数(ループ実装) = " + count); + System.out.println("指数時間(ループ実装)の操作回数 = " + count); count = expRecur(n); - System.out.println("指数計算量の操作回数(再帰実装) = " + count); + System.out.println("指数時間(再帰実装)の操作回数 = " + count); count = logarithmic(n); - System.out.println("対数計算量の操作回数(ループ実装) = " + count); + System.out.println("対数時間(ループ実装)の操作回数 = " + count); count = logRecur(n); - System.out.println("対数計算量の操作回数(再帰実装) = " + count); + System.out.println("対数時間(再帰実装)の操作回数 = " + count); count = linearLogRecur(n); - System.out.println("線形対数計算量の操作回数(再帰実装) = " + count); + System.out.println("線形対数時間(再帰実装)の操作回数 = " + count); count = factorialRecur(n); - System.out.println("階乗計算量の操作回数(再帰実装) = " + count); + System.out.println("階乗時間(再帰実装)の操作回数 = " + count); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_computational_complexity/worst_best_time_complexity.java b/ja/codes/java/chapter_computational_complexity/worst_best_time_complexity.java index 58d99eb66..6e0c915b1 100644 --- a/ja/codes/java/chapter_computational_complexity/worst_best_time_complexity.java +++ b/ja/codes/java/chapter_computational_complexity/worst_best_time_complexity.java @@ -9,7 +9,7 @@ package chapter_computational_complexity; import java.util.*; public class worst_best_time_complexity { - /* 要素 {1, 2, ..., n} をランダムにシャッフルした配列を生成 */ + /* 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 */ static int[] randomNumbers(int n) { Integer[] nums = new Integer[n]; // 配列 nums = { 1, 2, 3, ..., n } を生成 @@ -26,25 +26,25 @@ public class worst_best_time_complexity { return res; } - /* 配列 nums で数値1のインデックスを見つける */ + /* 配列 nums 内で数値 1 のインデックスを探す */ static int findOne(int[] nums) { for (int i = 0; i < nums.length; i++) { - // 要素1が配列の先頭にある場合、最良時間計算量 O(1) を達成 - // 要素1が配列の末尾にある場合、最悪時間計算量 O(n) を達成 + // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる + // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる if (nums[i] == 1) return i; } return -1; } - /* ドライバーコード */ + /* Driver Code */ public static void main(String[] args) { for (int i = 0; i < 10; i++) { int n = 100; int[] nums = randomNumbers(n); int index = findOne(nums); - System.out.println("\n配列 [ 1, 2, ..., n ] をシャッフル後 = " + Arrays.toString(nums)); - System.out.println("数値1のインデックスは " + index); + System.out.println("\n配列 [ 1, 2, ..., n ] をシャッフルした後 = " + Arrays.toString(nums)); + System.out.println("数字 1 のインデックスは " + index); } } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_divide_and_conquer/binary_search_recur.java b/ja/codes/java/chapter_divide_and_conquer/binary_search_recur.java index 0ca339e04..fd28f7982 100644 --- a/ja/codes/java/chapter_divide_and_conquer/binary_search_recur.java +++ b/ja/codes/java/chapter_divide_and_conquer/binary_search_recur.java @@ -9,20 +9,20 @@ package chapter_divide_and_conquer; public class binary_search_recur { /* 二分探索:問題 f(i, j) */ static int dfs(int[] nums, int target, int i, int j) { - // 区間が空の場合、対象要素が存在しないことを示すため、-1 を返す + // 区間が空なら対象要素は存在しないので -1 を返す if (i > j) { return -1; } // 中点インデックス m を計算 - int m = i + (j - i) / 2; + int m = (i + j) / 2; if (nums[m] < target) { - // 再帰的な部分問題 f(m+1, j) + // 部分問題 f(m+1, j) を再帰的に解く return dfs(nums, target, m + 1, j); } else if (nums[m] > target) { - // 再帰的な部分問題 f(i, m-1) + // 部分問題 f(i, m-1) を再帰的に解く return dfs(nums, target, i, m - 1); } else { - // 対象要素が見つかったため、そのインデックスを返す + // 目標要素が見つかったらそのインデックスを返す return m; } } @@ -38,8 +38,8 @@ public class binary_search_recur { int target = 6; int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }; - // 二分探索(両端閉区間) + // 二分探索(両閉区間) int index = binarySearch(nums, target); - System.out.println("対象要素 6 のインデックス =" + index); + System.out.println("対象要素 6 のインデックス = " + index); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_divide_and_conquer/build_tree.java b/ja/codes/java/chapter_divide_and_conquer/build_tree.java index d6738ddba..6cd31ab0c 100644 --- a/ja/codes/java/chapter_divide_and_conquer/build_tree.java +++ b/ja/codes/java/chapter_divide_and_conquer/build_tree.java @@ -10,26 +10,26 @@ import utils.*; import java.util.*; public class build_tree { - /* 二分木の構築:分割統治 */ + /* 二分木を構築:分割統治 */ static TreeNode dfs(int[] preorder, Map inorderMap, int i, int l, int r) { - // 部分木の区間が空の場合に終了 + // 部分木区間が空なら終了する if (r - l < 0) return null; - // ルートノードを初期化 + // ルートノードを初期化する TreeNode root = new TreeNode(preorder[i]); - // m を問い合わせて左右の部分木を分割 + // m を求めて左右部分木を分割する int m = inorderMap.get(preorder[i]); - // 部分問題:左の部分木を構築 + // 部分問題:左部分木を構築する root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); - // 部分問題:右の部分木を構築 + // 部分問題:右部分木を構築する root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); - // ルートノードを返す + // 根ノードを返す return root; } - /* 二分木の構築 */ + /* 二分木を構築 */ static TreeNode buildTree(int[] preorder, int[] inorder) { - // ハッシュテーブルを初期化し、中間順序の要素からインデックスへのマッピングを格納 + // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する Map inorderMap = new HashMap<>(); for (int i = 0; i < inorder.length; i++) { inorderMap.put(inorder[i], i); @@ -42,10 +42,10 @@ public class build_tree { int[] preorder = { 3, 9, 2, 1, 7 }; int[] inorder = { 9, 3, 1, 2, 7 }; System.out.println("前順走査 = " + Arrays.toString(preorder)); - System.out.println("中間順序走査 = " + Arrays.toString(inorder)); + System.out.println("中順走査 = " + Arrays.toString(inorder)); TreeNode root = buildTree(preorder, inorder); - System.out.println("構築された二分木:"); + System.out.println("構築した二分木は:"); PrintUtil.printTree(root); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_divide_and_conquer/hanota.java b/ja/codes/java/chapter_divide_and_conquer/hanota.java index a0046e481..dd597eced 100644 --- a/ja/codes/java/chapter_divide_and_conquer/hanota.java +++ b/ja/codes/java/chapter_divide_and_conquer/hanota.java @@ -9,51 +9,51 @@ package chapter_divide_and_conquer; import java.util.*; public class hanota { - /* 円盤を移動 */ + /* 円盤を 1 枚移動 */ static void move(List src, List tar) { - // src の最上部から円盤を取り出す + // src の上から円盤を1枚取り出す Integer pan = src.remove(src.size() - 1); - // 円盤を tar の最上部に配置 + // 円盤を tar の上に置く tar.add(pan); } - /* ハノイの塔問題 f(i) を解く */ + /* ハノイの塔の問題 f(i) を解く */ static void dfs(int i, List src, List buf, List tar) { - // src に円盤が1つだけ残っている場合、それを tar に移動 + // src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す if (i == 1) { move(src, tar); return; } - // 部分問題 f(i-1):tar の助けを借りて、上位 i-1 個の円盤を src から buf に移動 + // 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す dfs(i - 1, src, tar, buf); - // 部分問題 f(1):残りの1つの円盤を src から tar に移動 + // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す move(src, tar); - // 部分問題 f(i-1):src の助けを借りて、上位 i-1 個の円盤を buf から tar に移動 + // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す dfs(i - 1, buf, src, tar); } - /* ハノイの塔問題を解く */ + /* ハノイの塔を解く */ static void solveHanota(List A, List B, List C) { int n = A.size(); - // B の助けを借りて、上位 n 個の円盤を A から C に移動 + // A の上から n 枚の円盤を B を介して C へ移す dfs(n, A, B, C); } public static void main(String[] args) { - // リストの末尾が柱の最上部 + // リスト末尾が柱の頂上 List A = new ArrayList<>(Arrays.asList(5, 4, 3, 2, 1)); List B = new ArrayList<>(); List C = new ArrayList<>(); - System.out.println("初期状態:"); + System.out.println("初期状態:"); System.out.println("A = " + A); System.out.println("B = " + B); System.out.println("C = " + C); solveHanota(A, B, C); - System.out.println("円盤移動後:"); + System.out.println("円盤の移動完了後:"); System.out.println("A = " + A); System.out.println("B = " + B); System.out.println("C = " + C); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java b/ja/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java index cea633af8..d09a931c0 100644 --- a/ja/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java +++ b/ja/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java @@ -11,26 +11,26 @@ import java.util.*; public class climbing_stairs_backtrack { /* バックトラッキング */ public static void backtrack(List choices, int state, int n, List res) { - // n段目に到達したとき、解の数に1を加える + // 第 n 段に到達したら、方法数を 1 増やす if (state == n) res.set(0, res.get(0) + 1); // すべての選択肢を走査 for (Integer choice : choices) { - // 剪定:n段を超えて登ることを許可しない + // 枝刈り: 第 n 段を超えないようにする if (state + choice > n) continue; - // 試行:選択を行い、状態を更新 + // 試行: 選択を行い、状態を更新 backtrack(choices, state + choice, n, res); - // 撤回 + // バックトラック } } /* 階段登り:バックトラッキング */ public static int climbingStairsBacktrack(int n) { - List choices = Arrays.asList(1, 2); // 1段または2段登ることを選択可能 - int state = 0; // 0段目から登り始める + List choices = Arrays.asList(1, 2); // 1 段または 2 段上ることを選べる + int state = 0; // 第 0 段から上り始める List res = new ArrayList<>(); - res.add(0); // res[0] を使用して解の数を記録 + res.add(0); // res[0] を使って方法数を記録する backtrack(choices, state, n, res); return res.get(0); } @@ -39,6 +39,6 @@ public class climbing_stairs_backtrack { int n = 9; int res = climbingStairsBacktrack(n); - System.out.println(String.format("%d段の階段を登る解は%d通りです", n, res)); + System.out.println(String.format("%d 段の階段の登り方は全部で %d 通り", n, res)); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java b/ja/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java index 2f6bc9734..61781cab4 100644 --- a/ja/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java +++ b/ja/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java @@ -7,19 +7,19 @@ package chapter_dynamic_programming; public class climbing_stairs_constraint_dp { - /* 制約付き階段登り:動的プログラミング */ + /* 制約付き階段登り:動的計画法 */ static int climbingStairsConstraintDP(int n) { if (n == 1 || n == 2) { return 1; } - // DPテーブルを初期化し、部分問題の解を格納するために使用 + // 部分問題の解を保存するために dp テーブルを初期化 int[][] dp = new int[n + 1][3]; - // 初期状態:最小の部分問題の解を事前設定 + // 初期状態:最小部分問題の解をあらかじめ設定 dp[1][1] = 1; dp[1][2] = 0; dp[2][1] = 0; dp[2][2] = 1; - // 状態遷移:小さな問題から大きな部分問題を段階的に解く + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (int i = 3; i <= n; i++) { dp[i][1] = dp[i - 1][2]; dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; @@ -31,6 +31,6 @@ public class climbing_stairs_constraint_dp { int n = 9; int res = climbingStairsConstraintDP(n); - System.out.println(String.format("%d段の階段を登る解は%d通りです", n, res)); + System.out.println(String.format("%d 段の階段の登り方は全部で %d 通り", n, res)); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java b/ja/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java index 177b09b11..09a63d8f9 100644 --- a/ja/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java +++ b/ja/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java @@ -7,9 +7,9 @@ package chapter_dynamic_programming; public class climbing_stairs_dfs { - /* 探索 */ + /* 検索 */ public static int dfs(int i) { - // 既知の dp[1] と dp[2] を返す + // dp[1] と dp[2] は既知なので返す if (i == 1 || i == 2) return i; // dp[i] = dp[i-1] + dp[i-2] @@ -26,6 +26,6 @@ public class climbing_stairs_dfs { int n = 9; int res = climbingStairsDFS(n); - System.out.println(String.format("%d段の階段を登る解は%d通りです", n, res)); + System.out.println(String.format("%d 段の階段の登り方は全部で %d 通り", n, res)); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java b/ja/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java index 6c4be249c..1bb72b0ea 100644 --- a/ja/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java +++ b/ja/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java @@ -11,22 +11,22 @@ import java.util.Arrays; public class climbing_stairs_dfs_mem { /* メモ化探索 */ public static int dfs(int i, int[] mem) { - // 既知の dp[1] と dp[2] を返す + // dp[1] と dp[2] は既知なので返す if (i == 1 || i == 2) return i; - // dp[i] の記録がある場合、それを返す + // dp[i] の記録があれば、それをそのまま返す if (mem[i] != -1) return mem[i]; // dp[i] = dp[i-1] + dp[i-2] int count = dfs(i - 1, mem) + dfs(i - 2, mem); - // dp[i] を記録 + // dp[i] を記録する mem[i] = count; return count; } /* 階段登り:メモ化探索 */ public static int climbingStairsDFSMem(int n) { - // mem[i] は i 段目に登る総解数を記録、-1 は記録なしを意味する + // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す int[] mem = new int[n + 1]; Arrays.fill(mem, -1); return dfs(n, mem); @@ -36,6 +36,6 @@ public class climbing_stairs_dfs_mem { int n = 9; int res = climbingStairsDFSMem(n); - System.out.println(String.format("%d段の階段を登る解は%d通りです", n, res)); + System.out.println(String.format("%d 段の階段の登り方は全部で %d 通り", n, res)); } } \ No newline at end of file diff --git a/ja/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java b/ja/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java index d2be31b55..bdefa4224 100644 --- a/ja/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java +++ b/ja/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java @@ -7,23 +7,23 @@ package chapter_dynamic_programming; public class climbing_stairs_dp { - /* 階段登り:動的プログラミング */ + /* 階段登り:動的計画法 */ public static int climbingStairsDP(int n) { if (n == 1 || n == 2) return n; - // DPテーブルを初期化し、部分問題の解を格納するために使用 + // 部分問題の解を保存するために dp テーブルを初期化 int[] dp = new int[n + 1]; - // 初期状態:最小の部分問題の解を事前設定 + // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = 1; dp[2] = 2; - // 状態遷移:小さな問題から大きな部分問題を段階的に解く + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (int i = 3; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } - /* 階段登り:空間最適化動的プログラミング */ + /* 階段登り:空間最適化した動的計画法 */ public static int climbingStairsDPComp(int n) { if (n == 1 || n == 2) return n; @@ -40,9 +40,9 @@ public class climbing_stairs_dp { int n = 9; int res = climbingStairsDP(n); - System.out.println(String.format("%d段の階段を登る解は%d通りです", n, res)); + System.out.println(String.format("%d 段の階段の登り方は全部で %d 通り", n, res)); res = climbingStairsDPComp(n); - System.out.println(String.format("%d段の階段を登る解は%d通りです", n, res)); + System.out.println(String.format("%d 段の階段の登り方は全部で %d 通り", n, res)); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_dynamic_programming/coin_change.java b/ja/codes/java/chapter_dynamic_programming/coin_change.java index d0478a68c..a7a7154e9 100644 --- a/ja/codes/java/chapter_dynamic_programming/coin_change.java +++ b/ja/codes/java/chapter_dynamic_programming/coin_change.java @@ -9,24 +9,24 @@ package chapter_dynamic_programming; import java.util.Arrays; public class coin_change { - /* 硬貨両替:動的プログラミング */ + /* コイン両替:動的計画法 */ static int coinChangeDP(int[] coins, int amt) { int n = coins.length; int MAX = amt + 1; - // DPテーブルを初期化 + // dp テーブルを初期化 int[][] dp = new int[n + 1][amt + 1]; - // 状態遷移:最初の行と最初の列 + // 状態遷移:先頭行と先頭列 for (int a = 1; a <= amt; a++) { dp[0][a] = MAX; } - // 状態遷移:残りの行と列 + // 状態遷移: 残りの行と列 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { - // 目標金額を超える場合、硬貨 i を選択しない + // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a]; } else { - // 選択しない場合と硬貨 i を選択する場合のより小さい値 + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); } } @@ -34,11 +34,11 @@ public class coin_change { return dp[n][amt] != MAX ? dp[n][amt] : -1; } - /* 硬貨両替:空間最適化動的プログラミング */ + /* コイン交換:空間最適化後の動的計画法 */ static int coinChangeDPComp(int[] coins, int amt) { int n = coins.length; int MAX = amt + 1; - // DPテーブルを初期化 + // dp テーブルを初期化 int[] dp = new int[amt + 1]; Arrays.fill(dp, MAX); dp[0] = 0; @@ -46,10 +46,10 @@ public class coin_change { for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { - // 目標金額を超える場合、硬貨 i を選択しない + // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a]; } else { - // 選択しない場合と硬貨 i を選択する場合のより小さい値 + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); } } @@ -61,12 +61,12 @@ public class coin_change { int[] coins = { 1, 2, 5 }; int amt = 4; - // 動的プログラミング + // 動的計画法 int res = coinChangeDP(coins, amt); - System.out.println("目標金額を作るのに必要な最小硬貨数は " + res + " です"); + System.out.println("目標金額に必要な最小硬貨枚数は " + res); - // 空間最適化動的プログラミング + // 空間最適化後の動的計画法 res = coinChangeDPComp(coins, amt); - System.out.println("目標金額を作るのに必要な最小硬貨数は " + res + " です"); + System.out.println("目標金額に必要な最小硬貨枚数は " + res); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_dynamic_programming/coin_change_ii.java b/ja/codes/java/chapter_dynamic_programming/coin_change_ii.java index 58ec43948..d21af0ed8 100644 --- a/ja/codes/java/chapter_dynamic_programming/coin_change_ii.java +++ b/ja/codes/java/chapter_dynamic_programming/coin_change_ii.java @@ -7,12 +7,12 @@ package chapter_dynamic_programming; public class coin_change_ii { - /* 硬貨両替 II:動的プログラミング */ + /* コイン両替 II:動的計画法 */ static int coinChangeIIDP(int[] coins, int amt) { int n = coins.length; - // DPテーブルを初期化 + // dp テーブルを初期化 int[][] dp = new int[n + 1][amt + 1]; - // 最初の列を初期化 + // 先頭列を初期化する for (int i = 0; i <= n; i++) { dp[i][0] = 1; } @@ -20,10 +20,10 @@ public class coin_change_ii { for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { - // 目標金額を超える場合、硬貨 i を選択しない + // 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a]; } else { - // 選択しない場合と硬貨 i を選択する場合の2つの選択肢の合計 + // コイン i を選ばない場合と選ぶ場合の和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; } } @@ -31,20 +31,20 @@ public class coin_change_ii { return dp[n][amt]; } - /* 硬貨両替 II:空間最適化動的プログラミング */ + /* コイン両替 II:空間最適化した動的計画法 */ static int coinChangeIIDPComp(int[] coins, int amt) { int n = coins.length; - // DPテーブルを初期化 + // dp テーブルを初期化 int[] dp = new int[amt + 1]; dp[0] = 1; // 状態遷移 for (int i = 1; i <= n; i++) { for (int a = 1; a <= amt; a++) { if (coins[i - 1] > a) { - // 目標金額を超える場合、硬貨 i を選択しない + // 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a]; } else { - // 選択しない場合と硬貨 i を選択する場合の2つの選択肢の合計 + // コイン i を選ばない場合と選ぶ場合の和 dp[a] = dp[a] + dp[a - coins[i - 1]]; } } @@ -56,12 +56,12 @@ public class coin_change_ii { int[] coins = { 1, 2, 5 }; int amt = 5; - // 動的プログラミング + // 動的計画法 int res = coinChangeIIDP(coins, amt); - System.out.println("目標金額を作る硬貨の組み合わせ数は " + res + " です"); + System.out.println("目標金額を作る硬貨の組み合わせ数は " + res); - // 空間最適化動的プログラミング + // 空間最適化後の動的計画法 res = coinChangeIIDPComp(coins, amt); - System.out.println("目標金額を作る硬貨の組み合わせ数は " + res + " です"); + System.out.println("目標金額を作る硬貨の組み合わせ数は " + res); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_dynamic_programming/edit_distance.java b/ja/codes/java/chapter_dynamic_programming/edit_distance.java index bb54e62ac..d395448e9 100644 --- a/ja/codes/java/chapter_dynamic_programming/edit_distance.java +++ b/ja/codes/java/chapter_dynamic_programming/edit_distance.java @@ -9,73 +9,73 @@ package chapter_dynamic_programming; import java.util.Arrays; public class edit_distance { - /* 編集距離:ブルートフォース探索 */ + /* 編集距離:総当たり探索 */ static int editDistanceDFS(String s, String t, int i, int j) { - // s と t の両方が空の場合、0 を返す + // s と t がともに空なら 0 を返す if (i == 0 && j == 0) return 0; - // s が空の場合、t の長さを返す + // s が空なら t の長さを返す if (i == 0) return j; - // t が空の場合、s の長さを返す + // t が空なら s の長さを返す if (j == 0) return i; - // 2つの文字が等しい場合、これら2つの文字をスキップ + // 2 つの文字が等しければ、その 2 文字をそのままスキップする if (s.charAt(i - 1) == t.charAt(j - 1)) return editDistanceDFS(s, t, i - 1, j - 1); - // 最小編集数 = 3つの操作(挿入、削除、置換)からの最小編集数 + 1 + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 int insert = editDistanceDFS(s, t, i, j - 1); int delete = editDistanceDFS(s, t, i - 1, j); int replace = editDistanceDFS(s, t, i - 1, j - 1); - // 最小編集数を返す + // 最小編集回数を返す return Math.min(Math.min(insert, delete), replace) + 1; } /* 編集距離:メモ化探索 */ static int editDistanceDFSMem(String s, String t, int[][] mem, int i, int j) { - // s と t の両方が空の場合、0 を返す + // s と t がともに空なら 0 を返す if (i == 0 && j == 0) return 0; - // s が空の場合、t の長さを返す + // s が空なら t の長さを返す if (i == 0) return j; - // t が空の場合、s の長さを返す + // t が空なら s の長さを返す if (j == 0) return i; - // 記録がある場合、それを返す + // 記録済みなら、それをそのまま返す if (mem[i][j] != -1) return mem[i][j]; - // 2つの文字が等しい場合、これら2つの文字をスキップ + // 2 つの文字が等しければ、その 2 文字をそのままスキップする if (s.charAt(i - 1) == t.charAt(j - 1)) return editDistanceDFSMem(s, t, mem, i - 1, j - 1); - // 最小編集数 = 3つの操作(挿入、削除、置換)からの最小編集数 + 1 + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 int insert = editDistanceDFSMem(s, t, mem, i, j - 1); int delete = editDistanceDFSMem(s, t, mem, i - 1, j); int replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); - // 最小編集数を記録して返す + // 最小編集回数を記録して返す mem[i][j] = Math.min(Math.min(insert, delete), replace) + 1; return mem[i][j]; } - /* 編集距離:動的プログラミング */ + /* 編集距離:動的計画法 */ static int editDistanceDP(String s, String t) { int n = s.length(), m = t.length(); int[][] dp = new int[n + 1][m + 1]; - // 状態遷移:最初の行と最初の列 + // 状態遷移:先頭行と先頭列 for (int i = 1; i <= n; i++) { dp[i][0] = i; } for (int j = 1; j <= m; j++) { dp[0][j] = j; } - // 状態遷移:残りの行と列 + // 状態遷移: 残りの行と列 for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s.charAt(i - 1) == t.charAt(j - 1)) { - // 2つの文字が等しい場合、これら2つの文字をスキップ + // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[i][j] = dp[i - 1][j - 1]; } else { - // 最小編集数 = 3つの操作(挿入、削除、置換)からの最小編集数 + 1 + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[i][j] = Math.min(Math.min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; } } @@ -83,30 +83,30 @@ public class edit_distance { return dp[n][m]; } - /* 編集距離:空間最適化動的プログラミング */ + /* 編集距離:空間最適化した動的計画法 */ static int editDistanceDPComp(String s, String t) { int n = s.length(), m = t.length(); int[] dp = new int[m + 1]; - // 状態遷移:最初の行 + // 状態遷移:先頭行 for (int j = 1; j <= m; j++) { dp[j] = j; } // 状態遷移:残りの行 for (int i = 1; i <= n; i++) { - // 状態遷移:最初の列 - int leftup = dp[0]; // dp[i-1, j-1] を一時的に格納 + // 状態遷移:先頭列 + int leftup = dp[0]; // dp[i-1, j-1] を一時保存する dp[0] = i; // 状態遷移:残りの列 for (int j = 1; j <= m; j++) { int temp = dp[j]; if (s.charAt(i - 1) == t.charAt(j - 1)) { - // 2つの文字が等しい場合、これら2つの文字をスキップ + // 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[j] = leftup; } else { - // 最小編集数 = 3つの操作(挿入、削除、置換)からの最小編集数 + 1 + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[j] = Math.min(Math.min(dp[j - 1], dp[j]), leftup) + 1; } - leftup = temp; // 次のラウンドの dp[i-1, j-1] のために更新 + leftup = temp; // 次の反復の dp[i-1, j-1] に更新する } } return dp[m]; @@ -117,23 +117,23 @@ public class edit_distance { String t = "pack"; int n = s.length(), m = t.length(); - // ブルートフォース探索 + // 全探索 int res = editDistanceDFS(s, t, n, m); - System.out.println(s + " を " + t + " に変更するには最低 " + res + " 回の編集が必要です"); + System.out.println(s + " を " + t + " に変更するには、最小で " + res + " 回の編集が必要"); // メモ化探索 int[][] mem = new int[n + 1][m + 1]; for (int[] row : mem) Arrays.fill(row, -1); res = editDistanceDFSMem(s, t, mem, n, m); - System.out.println(s + " を " + t + " に変更するには最低 " + res + " 回の編集が必要です"); + System.out.println(s + " を " + t + " に変更するには、最小で " + res + " 回の編集が必要"); - // 動的プログラミング + // 動的計画法 res = editDistanceDP(s, t); - System.out.println(s + " を " + t + " に変更するには最低 " + res + " 回の編集が必要です"); + System.out.println(s + " を " + t + " に変更するには、最小で " + res + " 回の編集が必要"); - // 空間最適化動的プログラミング + // 空間最適化後の動的計画法 res = editDistanceDPComp(s, t); - System.out.println(s + " を " + t + " に変更するには最低 " + res + " 回の編集が必要です"); + System.out.println(s + " を " + t + " に変更するには、最小で " + res + " 回の編集が必要"); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_dynamic_programming/knapsack.java b/ja/codes/java/chapter_dynamic_programming/knapsack.java index 7826d1223..55ff2d542 100644 --- a/ja/codes/java/chapter_dynamic_programming/knapsack.java +++ b/ja/codes/java/chapter_dynamic_programming/knapsack.java @@ -10,58 +10,58 @@ import java.util.Arrays; public class knapsack { - /* 0-1 ナップサック:ブルートフォース探索 */ + /* 0-1 ナップサック:総当たり探索 */ static int knapsackDFS(int[] wgt, int[] val, int i, int c) { - // すべてのアイテムが選択されたか、ナップサックに残り容量がない場合、値 0 を返す + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if (i == 0 || c == 0) { return 0; } - // ナップサックの容量を超える場合、ナップサックに入れないことしか選択できない + // ナップサック容量を超える場合は、入れない選択しかできない if (wgt[i - 1] > c) { return knapsackDFS(wgt, val, i - 1, c); } - // アイテム i を入れない場合と入れる場合の最大値を計算 + // 品物 i を入れない場合と入れる場合の最大価値を計算する int no = knapsackDFS(wgt, val, i - 1, c); int yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; - // 2つの選択肢のより大きい値を返す + // 2つの案のうち価値が大きいほうを返す return Math.max(no, yes); } /* 0-1 ナップサック:メモ化探索 */ static int knapsackDFSMem(int[] wgt, int[] val, int[][] mem, int i, int c) { - // すべてのアイテムが選択されたか、ナップサックに残り容量がない場合、値 0 を返す + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if (i == 0 || c == 0) { return 0; } - // 記録がある場合、それを返す + // 既に記録があればそのまま返す if (mem[i][c] != -1) { return mem[i][c]; } - // ナップサックの容量を超える場合、ナップサックに入れないことしか選択できない + // ナップサック容量を超える場合は、入れない選択しかできない if (wgt[i - 1] > c) { return knapsackDFSMem(wgt, val, mem, i - 1, c); } - // アイテム i を入れない場合と入れる場合の最大値を計算 + // 品物 i を入れない場合と入れる場合の最大価値を計算する int no = knapsackDFSMem(wgt, val, mem, i - 1, c); int yes = knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; - // 2つの選択肢のより大きい値を記録して返す + // 2 つの案のうち価値が大きい方を記録して返す mem[i][c] = Math.max(no, yes); return mem[i][c]; } - /* 0-1 ナップサック:動的プログラミング */ + /* 0-1 ナップサック:動的計画法 */ static int knapsackDP(int[] wgt, int[] val, int cap) { int n = wgt.length; - // DPテーブルを初期化 + // dp テーブルを初期化 int[][] dp = new int[n + 1][cap + 1]; // 状態遷移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { - // ナップサックの容量を超える場合、アイテム i を選択しない + // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c]; } else { - // 選択しない場合とアイテム i を選択する場合のより大きい値 + // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = Math.max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]); } } @@ -69,17 +69,17 @@ public class knapsack { return dp[n][cap]; } - /* 0-1 ナップサック:空間最適化動的プログラミング */ + /* 0-1 ナップサック:空間最適化後の動的計画法 */ static int knapsackDPComp(int[] wgt, int[] val, int cap) { int n = wgt.length; - // DPテーブルを初期化 + // dp テーブルを初期化 int[] dp = new int[cap + 1]; // 状態遷移 for (int i = 1; i <= n; i++) { - // 逆順で走査 + // 逆順に走査する for (int c = cap; c >= 1; c--) { if (wgt[i - 1] <= c) { - // 選択しない場合とアイテム i を選択する場合のより大きい値 + // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } @@ -93,9 +93,9 @@ public class knapsack { int cap = 50; int n = wgt.length; - // ブルートフォース探索 + // 全探索 int res = knapsackDFS(wgt, val, n, cap); - System.out.println("ナップサック容量内での最大値は " + res + " です"); + System.out.println("ナップサック容量を超えない最大価値は " + res); // メモ化探索 int[][] mem = new int[n + 1][cap + 1]; @@ -103,14 +103,14 @@ public class knapsack { Arrays.fill(row, -1); } res = knapsackDFSMem(wgt, val, mem, n, cap); - System.out.println("ナップサック容量内での最大値は " + res + " です"); + System.out.println("ナップサック容量を超えない最大価値は " + res); - // 動的プログラミング + // 動的計画法 res = knapsackDP(wgt, val, cap); - System.out.println("ナップサック容量内での最大値は " + res + " です"); + System.out.println("ナップサック容量を超えない最大価値は " + res); - // 空間最適化動的プログラミング + // 空間最適化後の動的計画法 res = knapsackDPComp(wgt, val, cap); - System.out.println("ナップサック容量内での最大値は " + res + " です"); + System.out.println("ナップサック容量を超えない最大価値は " + res); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java b/ja/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java index 3b7b35250..e51243400 100644 --- a/ja/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java +++ b/ja/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java @@ -9,24 +9,24 @@ package chapter_dynamic_programming; import java.util.Arrays; public class min_cost_climbing_stairs_dp { - /* 最小コスト階段登り:動的プログラミング */ + /* 階段登りの最小コスト:動的計画法 */ public static int minCostClimbingStairsDP(int[] cost) { int n = cost.length - 1; if (n == 1 || n == 2) return cost[n]; - // DPテーブルを初期化し、部分問題の解を格納するために使用 + // 部分問題の解を保存するために dp テーブルを初期化 int[] dp = new int[n + 1]; - // 初期状態:最小の部分問題の解を事前設定 + // 初期状態:最小部分問題の解をあらかじめ設定 dp[1] = cost[1]; dp[2] = cost[2]; - // 状態遷移:小さな問題から大きな部分問題を段階的に解く + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く for (int i = 3; i <= n; i++) { dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; } return dp[n]; } - /* 最小コスト階段登り:空間最適化動的プログラミング */ + /* 階段昇りの最小コスト:空間最適化後の動的計画法 */ public static int minCostClimbingStairsDPComp(int[] cost) { int n = cost.length - 1; if (n == 1 || n == 2) @@ -42,12 +42,12 @@ public class min_cost_climbing_stairs_dp { public static void main(String[] args) { int[] cost = { 0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1 }; - System.out.println(String.format("階段のコストリストを %s として入力", Arrays.toString(cost))); + System.out.println(String.format("入力された階段コストのリストは %s", Arrays.toString(cost))); int res = minCostClimbingStairsDP(cost); - System.out.println(String.format("階段を登るための最小コスト %d", res)); + System.out.println(String.format("階段を上り切る最小コストは %d", res)); res = minCostClimbingStairsDPComp(cost); - System.out.println(String.format("階段を登るための最小コスト %d", res)); + System.out.println(String.format("階段を上り切る最小コストは %d", res)); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_dynamic_programming/min_path_sum.java b/ja/codes/java/chapter_dynamic_programming/min_path_sum.java index c7860e1f7..b8f11a5e4 100644 --- a/ja/codes/java/chapter_dynamic_programming/min_path_sum.java +++ b/ja/codes/java/chapter_dynamic_programming/min_path_sum.java @@ -9,60 +9,60 @@ package chapter_dynamic_programming; import java.util.Arrays; public class min_path_sum { - /* 最小パス和:ブルートフォース探索 */ + /* 最小経路和:全探索 */ static int minPathSumDFS(int[][] grid, int i, int j) { - // 左上のセルの場合、探索を終了 + // 左上のセルなら探索を終了する if (i == 0 && j == 0) { return grid[0][0]; } - // 行または列のインデックスが範囲外の場合、+∞ のコストを返す + // 行または列のインデックスが範囲外なら、コスト +∞ を返す if (i < 0 || j < 0) { return Integer.MAX_VALUE; } - // 左上から (i-1, j) と (i, j-1) への最小パスコストを計算 + // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する int up = minPathSumDFS(grid, i - 1, j); int left = minPathSumDFS(grid, i, j - 1); - // 左上から (i, j) への最小パスコストを返す + // 左上隅から (i, j) までの最小経路コストを返す return Math.min(left, up) + grid[i][j]; } - /* 最小パス和:メモ化探索 */ + /* 最小経路和:メモ化探索 */ static int minPathSumDFSMem(int[][] grid, int[][] mem, int i, int j) { - // 左上のセルの場合、探索を終了 + // 左上のセルなら探索を終了する if (i == 0 && j == 0) { return grid[0][0]; } - // 行または列のインデックスが範囲外の場合、+∞ のコストを返す + // 行または列のインデックスが範囲外なら、コスト +∞ を返す if (i < 0 || j < 0) { return Integer.MAX_VALUE; } - // 記録がある場合、それを返す + // 既に記録があればそのまま返す if (mem[i][j] != -1) { return mem[i][j]; } - // 左と上のセルからの最小パスコスト + // 左と上のセルからの最小経路コスト int up = minPathSumDFSMem(grid, mem, i - 1, j); int left = minPathSumDFSMem(grid, mem, i, j - 1); - // 左上から (i, j) への最小パスコストを記録して返す + // 左上から (i, j) までの最小経路コストを記録して返す mem[i][j] = Math.min(left, up) + grid[i][j]; return mem[i][j]; } - /* 最小パス和:動的プログラミング */ + /* 最小経路和:動的計画法 */ static int minPathSumDP(int[][] grid) { int n = grid.length, m = grid[0].length; - // DPテーブルを初期化 + // dp テーブルを初期化 int[][] dp = new int[n][m]; dp[0][0] = grid[0][0]; - // 状態遷移:最初の行 + // 状態遷移:先頭行 for (int j = 1; j < m; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } - // 状態遷移:最初の列 + // 状態遷移:先頭列 for (int i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } - // 状態遷移:残りの行と列 + // 状態遷移: 残りの行と列 for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; @@ -71,19 +71,19 @@ public class min_path_sum { return dp[n - 1][m - 1]; } - /* 最小パス和:空間最適化動的プログラミング */ + /* 最小経路和:空間最適化後の動的計画法 */ static int minPathSumDPComp(int[][] grid) { int n = grid.length, m = grid[0].length; - // DPテーブルを初期化 + // dp テーブルを初期化 int[] dp = new int[m]; - // 状態遷移:最初の行 + // 状態遷移:先頭行 dp[0] = grid[0][0]; for (int j = 1; j < m; j++) { dp[j] = dp[j - 1] + grid[0][j]; } // 状態遷移:残りの行 for (int i = 1; i < n; i++) { - // 状態遷移:最初の列 + // 状態遷移:先頭列 dp[0] = dp[0] + grid[i][0]; // 状態遷移:残りの列 for (int j = 1; j < m; j++) { @@ -102,9 +102,9 @@ public class min_path_sum { }; int n = grid.length, m = grid[0].length; - // ブルートフォース探索 + // 全探索 int res = minPathSumDFS(grid, n - 1, m - 1); - System.out.println("左上角から右下角への最小パス和は " + res + " です"); + System.out.println("左上から右下までの最小経路和は " + res); // メモ化探索 int[][] mem = new int[n][m]; @@ -112,14 +112,14 @@ public class min_path_sum { Arrays.fill(row, -1); } res = minPathSumDFSMem(grid, mem, n - 1, m - 1); - System.out.println("左上角から右下角への最小パス和は " + res + " です"); + System.out.println("左上から右下までの最小経路和は " + res); - // 動的プログラミング + // 動的計画法 res = minPathSumDP(grid); - System.out.println("左上角から右下角への最小パス和は " + res + " です"); + System.out.println("左上から右下までの最小経路和は " + res); - // 空間最適化動的プログラミング + // 空間最適化後の動的計画法 res = minPathSumDPComp(grid); - System.out.println("左上角から右下角への最小パス和は " + res + " です"); + System.out.println("左上から右下までの最小経路和は " + res); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_dynamic_programming/unbounded_knapsack.java b/ja/codes/java/chapter_dynamic_programming/unbounded_knapsack.java index 99e7f41f2..e4a540c44 100644 --- a/ja/codes/java/chapter_dynamic_programming/unbounded_knapsack.java +++ b/ja/codes/java/chapter_dynamic_programming/unbounded_knapsack.java @@ -7,19 +7,19 @@ package chapter_dynamic_programming; public class unbounded_knapsack { - /* 完全ナップサック:動的プログラミング */ + /* 完全ナップサック問題:動的計画法 */ static int unboundedKnapsackDP(int[] wgt, int[] val, int cap) { int n = wgt.length; - // DPテーブルを初期化 + // dp テーブルを初期化 int[][] dp = new int[n + 1][cap + 1]; // 状態遷移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { - // ナップサックの容量を超える場合、アイテム i を選択しない + // ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c]; } else { - // 選択しない場合とアイテム i を選択する場合のより大きい値 + // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = Math.max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]); } } @@ -27,19 +27,19 @@ public class unbounded_knapsack { return dp[n][cap]; } - /* 完全ナップサック:空間最適化動的プログラミング */ + /* 完全ナップサック問題:空間最適化後の動的計画法 */ static int unboundedKnapsackDPComp(int[] wgt, int[] val, int cap) { int n = wgt.length; - // DPテーブルを初期化 + // dp テーブルを初期化 int[] dp = new int[cap + 1]; // 状態遷移 for (int i = 1; i <= n; i++) { for (int c = 1; c <= cap; c++) { if (wgt[i - 1] > c) { - // ナップサックの容量を超える場合、アイテム i を選択しない + // ナップサック容量を超えるなら品物 i は選ばない dp[c] = dp[c]; } else { - // 選択しない場合とアイテム i を選択する場合のより大きい値 + // 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); } } @@ -52,12 +52,12 @@ public class unbounded_knapsack { int[] val = { 5, 11, 15 }; int cap = 4; - // 動的プログラミング + // 動的計画法 int res = unboundedKnapsackDP(wgt, val, cap); - System.out.println("ナップサック容量内での最大値は " + res + " です"); + System.out.println("ナップサック容量を超えない最大価値は " + res); - // 空間最適化動的プログラミング + // 空間最適化後の動的計画法 res = unboundedKnapsackDPComp(wgt, val, cap); - System.out.println("ナップサック容量内での最大値は " + res + " です"); + System.out.println("ナップサック容量を超えない最大価値は " + res); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_graph/graph_adjacency_list.java b/ja/codes/java/chapter_graph/graph_adjacency_list.java index 824e73d46..febd216ea 100644 --- a/ja/codes/java/chapter_graph/graph_adjacency_list.java +++ b/ja/codes/java/chapter_graph/graph_adjacency_list.java @@ -11,7 +11,7 @@ import utils.*; /* 隣接リストに基づく無向グラフクラス */ class GraphAdjList { - // 隣接リスト、キー: 頂点、値: その頂点のすべての隣接頂点 + // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 Map> adjList; /* コンストラクタ */ @@ -52,7 +52,7 @@ class GraphAdjList { public void addVertex(Vertex vet) { if (adjList.containsKey(vet)) return; - // 隣接リストに新しい連結リストを追加 + // 隣接リストに新しいリストを追加 adjList.put(vet, new ArrayList<>()); } @@ -60,9 +60,9 @@ class GraphAdjList { public void removeVertex(Vertex vet) { if (!adjList.containsKey(vet)) throw new IllegalArgumentException(); - // 隣接リストから頂点 vet に対応する連結リストを削除 + // 隣接リストから頂点 vet に対応するリストを削除 adjList.remove(vet); - // 他の頂点の連結リストを走査し、vet を含むすべての辺を削除 + // 他の頂点のリストを走査し、vet を含むすべての辺を削除 for (List list : adjList.values()) { list.remove(vet); } @@ -87,31 +87,31 @@ public class graph_adjacency_list { Vertex[][] edges = { { v[0], v[1] }, { v[0], v[3] }, { v[1], v[2] }, { v[2], v[3] }, { v[2], v[4] }, { v[3], v[4] } }; GraphAdjList graph = new GraphAdjList(edges); - System.out.println("\n初期化後、グラフは"); + System.out.println("\n初期化後のグラフ"); graph.print(); /* 辺を追加 */ - // 頂点 1、2、すなわち v[0]、v[2] + // 頂点 1, 2 は v[0], v[2] graph.addEdge(v[0], v[2]); - System.out.println("\n辺 1-2 を追加後、グラフは"); + System.out.println("\n辺 1-2 を追加した後のグラフ"); graph.print(); /* 辺を削除 */ - // 頂点 1、3、すなわち v[0]、v[1] + // 頂点 1, 3 は v[0], v[1] graph.removeEdge(v[0], v[1]); - System.out.println("\n辺 1-3 を削除後、グラフは"); + System.out.println("\n辺 1-3 を削除した後のグラフ"); graph.print(); /* 頂点を追加 */ Vertex v5 = new Vertex(6); graph.addVertex(v5); - System.out.println("\n頂点 6 を追加後、グラフは"); + System.out.println("\n頂点 6 を追加した後のグラフ"); graph.print(); /* 頂点を削除 */ - // 頂点 3、すなわち v[1] + // 頂点 3 は v[1] graph.removeVertex(v[1]); - System.out.println("\n頂点 3 を削除後、グラフは"); + System.out.println("\n頂点 3 を削除すると、グラフは"); graph.print(); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_graph/graph_adjacency_matrix.java b/ja/codes/java/chapter_graph/graph_adjacency_matrix.java index 55755d0b1..36fe8925f 100644 --- a/ja/codes/java/chapter_graph/graph_adjacency_matrix.java +++ b/ja/codes/java/chapter_graph/graph_adjacency_matrix.java @@ -11,8 +11,8 @@ import java.util.*; /* 隣接行列に基づく無向グラフクラス */ class GraphAdjMat { - List vertices; // 頂点リスト、要素は「頂点値」を表し、インデックスは「頂点インデックス」を表す - List> adjMat; // 隣接行列、行と列のインデックスは「頂点インデックス」に対応 + List vertices; // 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す + List> adjMat; // 隣接行列。行・列のインデックスは「頂点インデックス」に対応 /* コンストラクタ */ public GraphAdjMat(int[] vertices, int[][] edges) { @@ -23,7 +23,7 @@ class GraphAdjMat { addVertex(val); } // 辺を追加 - // 辺の要素は頂点インデックスを表す + // 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する for (int[] e : edges) { addEdge(e[0], e[1]); } @@ -37,15 +37,15 @@ class GraphAdjMat { /* 頂点を追加 */ public void addVertex(int val) { int n = size(); - // 頂点リストに新しい頂点値を追加 + // 頂点リストに新しい頂点の値を追加 vertices.add(val); - // 隣接行列に行を追加 + // 隣接行列に 1 行追加 List newRow = new ArrayList<>(n); for (int j = 0; j < n; j++) { newRow.add(0); } adjMat.add(newRow); - // 隣接行列に列を追加 + // 隣接行列に 1 列追加 for (List row : adjMat) { row.add(0); } @@ -55,31 +55,31 @@ class GraphAdjMat { public void removeVertex(int index) { if (index >= size()) throw new IndexOutOfBoundsException(); - // 頂点リストから `index` の頂点を削除 + // 頂点リストから index の頂点を削除する vertices.remove(index); - // 隣接行列から `index` の行を削除 + // 隣接行列で index 行を削除する adjMat.remove(index); - // 隣接行列から `index` の列を削除 + // 隣接行列で index 列を削除する for (List row : adjMat) { row.remove(index); } } /* 辺を追加 */ - // パラメータ i、j は頂点要素のインデックスに対応 + // 引数 i, j は vertices の要素インデックスに対応する public void addEdge(int i, int j) { - // インデックスの範囲外と等価性を処理 + // インデックスの範囲外と等値の処理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw new IndexOutOfBoundsException(); - // 無向グラフでは、隣接行列は主対角線について対称、すなわち (i, j) == (j, i) を満たす + // 無向グラフでは、隣接行列は主対角線に関して対称、すなわち (i, j) == (j, i) を満たす adjMat.get(i).set(j, 1); adjMat.get(j).set(i, 1); } /* 辺を削除 */ - // パラメータ i、j は頂点要素のインデックスに対応 + // 引数 i, j は vertices の要素インデックスに対応する public void removeEdge(int i, int j) { - // インデックスの範囲外と等価性を処理 + // インデックスの範囲外と等値の処理 if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) throw new IndexOutOfBoundsException(); adjMat.get(i).set(j, 0); @@ -98,34 +98,34 @@ class GraphAdjMat { public class graph_adjacency_matrix { public static void main(String[] args) { /* 無向グラフを初期化 */ - // 辺の要素は頂点インデックスを表す + // edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意 int[] vertices = { 1, 3, 2, 5, 4 }; int[][] edges = { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 2, 3 }, { 2, 4 }, { 3, 4 } }; GraphAdjMat graph = new GraphAdjMat(vertices, edges); - System.out.println("\n初期化後、グラフは"); + System.out.println("\n初期化後のグラフ"); graph.print(); /* 辺を追加 */ - // 頂点 1、2 のインデックスはそれぞれ 0、2 + // 頂点 1, 2 のインデックスはそれぞれ 0, 2 graph.addEdge(0, 2); - System.out.println("\n辺 1-2 を追加後、グラフは"); + System.out.println("\n辺 1-2 を追加した後のグラフ"); graph.print(); /* 辺を削除 */ - // 頂点 1、3 のインデックスはそれぞれ 0、1 + // 頂点 1, 3 のインデックスはそれぞれ 0, 1 graph.removeEdge(0, 1); - System.out.println("\n辺 1-3 を削除後、グラフは"); + System.out.println("\n辺 1-3 を削除した後のグラフ"); graph.print(); /* 頂点を追加 */ graph.addVertex(6); - System.out.println("\n頂点 6 を追加後、グラフは"); + System.out.println("\n頂点 6 を追加した後のグラフ"); graph.print(); /* 頂点を削除 */ // 頂点 3 のインデックスは 1 graph.removeVertex(1); - System.out.println("\n頂点 3 を削除後、グラフは"); + System.out.println("\n頂点 3 を削除すると、グラフは"); graph.print(); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_graph/graph_bfs.java b/ja/codes/java/chapter_graph/graph_bfs.java index eca03f12e..9bdcbf9bd 100644 --- a/ja/codes/java/chapter_graph/graph_bfs.java +++ b/ja/codes/java/chapter_graph/graph_bfs.java @@ -10,30 +10,30 @@ import java.util.*; import utils.*; public class graph_bfs { - /* 幅優先走査 */ - // 隣接リストを使用してグラフを表現し、指定した頂点のすべての隣接頂点を取得 + /* 幅優先探索 */ + // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする static List graphBFS(GraphAdjList graph, Vertex startVet) { - // 頂点走査順序 + // 頂点の走査順序 List res = new ArrayList<>(); - // ハッシュセット、訪問済みの頂点を記録するために使用 + // 訪問済み頂点を記録するためのハッシュ集合 Set visited = new HashSet<>(); visited.add(startVet); - // BFS を実装するために使用するキュー + // BFS の実装にキューを用いる Queue que = new LinkedList<>(); que.offer(startVet); - // 頂点 vet から開始し、すべての頂点が訪問されるまでループ + // 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す while (!que.isEmpty()) { - Vertex vet = que.poll(); // キューの先頭の頂点をデキュー + Vertex vet = que.poll(); // 先頭の頂点をデキュー res.add(vet); // 訪問した頂点を記録 - // その頂点のすべての隣接頂点を走査 + // この頂点のすべての隣接頂点を走査 for (Vertex adjVet : graph.adjList.get(vet)) { if (visited.contains(adjVet)) - continue; // すでに訪問済みの頂点をスキップ - que.offer(adjVet); // 未訪問の頂点のみをエンキュー - visited.add(adjVet); // 頂点を訪問済みとしてマーク + continue; // 訪問済みの頂点をスキップ + que.offer(adjVet); // 未訪問の頂点のみをキューに追加 + visited.add(adjVet); // この頂点を訪問済みにする } } - // 頂点走査順序を返す + // 頂点の走査順を返す return res; } @@ -44,12 +44,12 @@ public class graph_bfs { { v[2], v[5] }, { v[3], v[4] }, { v[3], v[6] }, { v[4], v[5] }, { v[4], v[7] }, { v[5], v[8] }, { v[6], v[7] }, { v[7], v[8] } }; GraphAdjList graph = new GraphAdjList(edges); - System.out.println("\n初期化後、グラフは"); + System.out.println("\n初期化後のグラフ"); graph.print(); - /* 幅優先走査 */ + /* 幅優先探索 */ List res = graphBFS(graph, v[0]); - System.out.println("\n幅優先走査 (BFS) の頂点順序は"); + System.out.println("\n幅優先探索(BFS)の頂点列は"); System.out.println(Vertex.vetsToVals(res)); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_graph/graph_dfs.java b/ja/codes/java/chapter_graph/graph_dfs.java index f8619af18..de33537f4 100644 --- a/ja/codes/java/chapter_graph/graph_dfs.java +++ b/ja/codes/java/chapter_graph/graph_dfs.java @@ -13,22 +13,22 @@ public class graph_dfs { /* 深さ優先走査の補助関数 */ static void dfs(GraphAdjList graph, Set visited, List res, Vertex vet) { res.add(vet); // 訪問した頂点を記録 - visited.add(vet); // 頂点を訪問済みとしてマーク - // その頂点のすべての隣接頂点を走査 + visited.add(vet); // この頂点を訪問済みにする + // この頂点のすべての隣接頂点を走査 for (Vertex adjVet : graph.adjList.get(vet)) { if (visited.contains(adjVet)) - continue; // すでに訪問済みの頂点をスキップ + continue; // 訪問済みの頂点をスキップ // 隣接頂点を再帰的に訪問 dfs(graph, visited, res, adjVet); } } - /* 深さ優先走査 */ - // 隣接リストを使用してグラフを表現し、指定した頂点のすべての隣接頂点を取得 + /* 深さ優先探索 */ + // グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする static List graphDFS(GraphAdjList graph, Vertex startVet) { - // 頂点走査順序 + // 頂点の走査順序 List res = new ArrayList<>(); - // ハッシュセット、訪問済みの頂点を記録するために使用 + // 訪問済み頂点を記録するためのハッシュ集合 Set visited = new HashSet<>(); dfs(graph, visited, res, startVet); return res; @@ -40,12 +40,12 @@ public class graph_dfs { Vertex[][] edges = { { v[0], v[1] }, { v[0], v[3] }, { v[1], v[2] }, { v[2], v[5] }, { v[4], v[5] }, { v[5], v[6] } }; GraphAdjList graph = new GraphAdjList(edges); - System.out.println("\n初期化後、グラフは"); + System.out.println("\n初期化後のグラフ"); graph.print(); - /* 深さ優先走査 */ + /* 深さ優先探索 */ List res = graphDFS(graph, v[0]); - System.out.println("\n深さ優先走査 (DFS) の頂点順序は"); + System.out.println("\n深さ優先探索(DFS)の頂点列は"); System.out.println(Vertex.vetsToVals(res)); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_greedy/coin_change_greedy.java b/ja/codes/java/chapter_greedy/coin_change_greedy.java index fef56080d..d511f56a7 100644 --- a/ja/codes/java/chapter_greedy/coin_change_greedy.java +++ b/ja/codes/java/chapter_greedy/coin_change_greedy.java @@ -9,47 +9,47 @@ package chapter_greedy; import java.util.Arrays; public class coin_change_greedy { - /* 硬貨両替:貪欲法 */ + /* コイン交換:貪欲法 */ static int coinChangeGreedy(int[] coins, int amt) { - // 硬貨リストが順序付けされていると仮定 + // coins リストはソート済みと仮定する int i = coins.length - 1; int count = 0; - // 残り金額がなくなるまで貪欲選択をループ + // 残額がなくなるまで貪欲選択を繰り返す while (amt > 0) { - // 残り金額に近く、それ以下の最小硬貨を見つける + // 残額以下で最も近い硬貨を見つける while (i > 0 && coins[i] > amt) { i--; } - // coins[i] を選択 + // coins[i] を選択する amt -= coins[i]; count++; } - // 実行可能な解が見つからない場合、-1 を返す + // 実行可能な解が見つからなければ -1 を返す return amt == 0 ? count : -1; } public static void main(String[] args) { - // 貪欲法:大域最適解の発見を保証できる + // 貪欲法:大域最適解を保証できる int[] coins = { 1, 5, 10, 20, 50, 100 }; int amt = 186; int res = coinChangeGreedy(coins, amt); System.out.println("\ncoins = " + Arrays.toString(coins) + ", amt = " + amt); - System.out.println(amt + " を作るのに必要な最小硬貨数は " + res + " です"); + System.out.println("合計 " + amt + " に必要な最小硬貨枚数は " + res); - // 貪欲法:大域最適解の発見を保証できない + // 貪欲法:大域最適解を保証できない coins = new int[] { 1, 20, 50 }; amt = 60; res = coinChangeGreedy(coins, amt); System.out.println("\ncoins = " + Arrays.toString(coins) + ", amt = " + amt); - System.out.println(amt + " を作るのに必要な最小硬貨数は " + res + " です"); - System.out.println("実際には、最小必要数は 3 です。つまり、20 + 20 + 20"); + System.out.println("合計 " + amt + " に必要な最小硬貨枚数は " + res); + System.out.println("実際に必要な最小枚数は 3、つまり 20 + 20 + 20"); - // 貪欲法:大域最適解の発見を保証できない + // 貪欲法:大域最適解を保証できない coins = new int[] { 1, 49, 50 }; amt = 98; res = coinChangeGreedy(coins, amt); System.out.println("\ncoins = " + Arrays.toString(coins) + ", amt = " + amt); - System.out.println(amt + " を作るのに必要な最小硬貨数は " + res + " です"); - System.out.println("実際には、最小必要数は 2 です。つまり、49 + 49"); + System.out.println("合計 " + amt + " に必要な最小硬貨枚数は " + res); + System.out.println("実際に必要な最小枚数は 2、つまり 49 + 49"); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_greedy/fractional_knapsack.java b/ja/codes/java/chapter_greedy/fractional_knapsack.java index 37c5298eb..51ef5292f 100644 --- a/ja/codes/java/chapter_greedy/fractional_knapsack.java +++ b/ja/codes/java/chapter_greedy/fractional_knapsack.java @@ -9,10 +9,10 @@ package chapter_greedy; import java.util.Arrays; import java.util.Comparator; -/* アイテム */ +/* 品物 */ class Item { - int w; // アイテムの重量 - int v; // アイテムの価値 + int w; // 品物の重さ + int v; // 品物の価値 public Item(int w, int v) { this.w = w; @@ -23,24 +23,24 @@ class Item { public class fractional_knapsack { /* 分数ナップサック:貪欲法 */ static double fractionalKnapsack(int[] wgt, int[] val, int cap) { - // アイテムリストを作成、2つの属性を含む:重量、価値 + // 重さと価値の 2 属性を持つ品物リストを作成 Item[] items = new Item[wgt.length]; for (int i = 0; i < wgt.length; i++) { items[i] = new Item(wgt[i], val[i]); } - // 単位価値 item.v / item.w で高い順にソート + // 単位価値 item.v / item.w の高い順にソートする Arrays.sort(items, Comparator.comparingDouble(item -> -((double) item.v / item.w))); - // 貪欲選択をループ + // 貪欲選択を繰り返す double res = 0; for (Item item : items) { if (item.w <= cap) { - // 残り容量が十分な場合、アイテム全体をナップサックに入れる + // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる res += item.v; cap -= item.w; } else { - // 残り容量が不十分な場合、アイテムの一部をナップサックに入れる + // 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる res += (double) item.v / item.w * cap; - // 残り容量がなくなったため、ループを中断 + // 残り容量がないため、ループを抜ける break; } } @@ -52,8 +52,8 @@ public class fractional_knapsack { int[] val = { 50, 120, 150, 210, 240 }; int cap = 50; - // 貪欲アルゴリズム + // 貪欲法 double res = fractionalKnapsack(wgt, val, cap); - System.out.println("ナップサック容量内での最大値は " + res + " です"); + System.out.println("ナップサック容量を超えない最大価値は " + res); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_greedy/max_capacity.java b/ja/codes/java/chapter_greedy/max_capacity.java index 01b7ea6f5..e76762a5f 100644 --- a/ja/codes/java/chapter_greedy/max_capacity.java +++ b/ja/codes/java/chapter_greedy/max_capacity.java @@ -9,16 +9,16 @@ package chapter_greedy; public class max_capacity { /* 最大容量:貪欲法 */ static int maxCapacity(int[] ht) { - // i、j を初期化し、配列の両端で分割させる + // i, j を初期化し、それぞれ配列の両端に置く int i = 0, j = ht.length - 1; - // 初期最大容量は 0 + // 初期の最大容量は 0 int res = 0; - // 2つの板が出会うまで貪欲選択をループ + // 2 枚の板が出会うまで貪欲選択を繰り返す while (i < j) { - // 最大容量を更新 + // 最大容量を更新する int cap = Math.min(ht[i], ht[j]) * (j - i); res = Math.max(res, cap); - // より短い板を内側に移動 + // 短い方を内側へ動かす if (ht[i] < ht[j]) { i++; } else { @@ -31,8 +31,8 @@ public class max_capacity { public static void main(String[] args) { int[] ht = { 3, 8, 5, 2, 7, 7, 3, 4 }; - // 貪欲アルゴリズム + // 貪欲法 int res = maxCapacity(ht); - System.out.println("最大容量は " + res + " です"); + System.out.println("最大容量は " + res); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_greedy/max_product_cutting.java b/ja/codes/java/chapter_greedy/max_product_cutting.java index f10de127d..9ef4dea09 100644 --- a/ja/codes/java/chapter_greedy/max_product_cutting.java +++ b/ja/codes/java/chapter_greedy/max_product_cutting.java @@ -9,32 +9,32 @@ package chapter_greedy; import java.lang.Math; public class max_product_cutting { - /* 最大積切断:貪欲法 */ + /* 最大切断積:貪欲法 */ public static int maxProductCutting(int n) { - // n <= 3 の場合、1 を切り出す必要がある + // n <= 3 のときは、必ず 1 を切り出す if (n <= 3) { return 1 * (n - 1); } - // 貪欲に 3 を切り出す。a は 3 の個数、b は余り + // 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする int a = n / 3; int b = n % 3; if (b == 1) { - // 余りが 1 の場合、1 * 3 のペアを 2 * 2 に変換 + // 余りが 1 のときは、1 * 3 を 2 * 2 に変える return (int) Math.pow(3, a - 1) * 2 * 2; } if (b == 2) { - // 余りが 2 の場合、何もしない + // 余りが 2 のときは、そのままにする return (int) Math.pow(3, a) * 2; } - // 余りが 0 の場合、何もしない + // 余りが 0 のときは、そのままにする return (int) Math.pow(3, a); } public static void main(String[] args) { int n = 58; - // 貪欲アルゴリズム + // 貪欲法 int res = maxProductCutting(n); - System.out.println("分割の最大積は " + res + " です"); + System.out.println("最大分割積は " + res); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_hashing/array_hash_map.java b/ja/codes/java/chapter_hashing/array_hash_map.java index f62b450fb..f3aa79326 100644 --- a/ja/codes/java/chapter_hashing/array_hash_map.java +++ b/ja/codes/java/chapter_hashing/array_hash_map.java @@ -8,7 +8,7 @@ package chapter_hashing; import java.util.*; -/* キー値ペア */ +/* キーと値の組 */ class Pair { public int key; public String val; @@ -19,12 +19,12 @@ class Pair { } } -/* 配列実装に基づくハッシュテーブル */ +/* 配列ベースのハッシュテーブル */ class ArrayHashMap { private List buckets; public ArrayHashMap() { - // 100個のバケットを含む配列を初期化 + // 100 個のバケットを含む配列を初期化 buckets = new ArrayList<>(); for (int i = 0; i < 100; i++) { buckets.add(null); @@ -37,7 +37,7 @@ class ArrayHashMap { return index; } - /* クエリ操作 */ + /* 検索操作 */ public String get(int key) { int index = hashFunc(key); Pair pair = buckets.get(index); @@ -56,11 +56,11 @@ class ArrayHashMap { /* 削除操作 */ public void remove(int key) { int index = hashFunc(key); - // nullに設定して削除を示す + // null に設定し、削除を表す buckets.set(index, null); } - /* すべてのキー値ペアを取得 */ + /* すべてのキーと値のペアを取得 */ public List pairSet() { List pairSet = new ArrayList<>(); for (Pair pair : buckets) { @@ -90,7 +90,7 @@ class ArrayHashMap { return valueSet; } - /* ハッシュテーブルを印刷 */ + /* ハッシュテーブルを出力 */ public void print() { for (Pair kv : pairSet()) { System.out.println(kv.key + " -> " + kv.val); @@ -104,38 +104,38 @@ public class array_hash_map { ArrayHashMap map = new ArrayHashMap(); /* 追加操作 */ - // ハッシュテーブルにキー値ペア (key, value) を追加 - map.put(12836, "Ha"); - map.put(15937, "Luo"); - map.put(16750, "Suan"); - map.put(13276, "Fa"); - map.put(10583, "Ya"); - System.out.println("\n追加後のハッシュテーブル\nKey -> Value"); + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.put(12836, "シャオハー"); + map.put(15937, "シャオルオ"); + map.put(16750, "シャオスワン"); + map.put(13276, "シャオファー"); + map.put(10583, "シャオヤー"); + System.out.println("\n追加後のハッシュ表は\nKey -> Value"); map.print(); - /* クエリ操作 */ - // ハッシュテーブルにキーを入力して値を取得 + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 String name = map.get(15937); - System.out.println("\n学生ID 15937を入力、名前 " + name + " を見つけました"); + System.out.println("\n学籍番号 15937 を入力すると、氏名 " + name); /* 削除操作 */ - // ハッシュテーブルからキー値ペア (key, value) を削除 + // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(10583); - System.out.println("\n10583を削除後のハッシュテーブル\nKey -> Value"); + System.out.println("\n10583 を削除すると、ハッシュ表は\nKey -> Value"); map.print(); /* ハッシュテーブルを走査 */ - System.out.println("\nキー値ペアを走査 Key->Value"); + System.out.println("\nキーと値の組 Key->Value を走査"); for (Pair kv : map.pairSet()) { System.out.println(kv.key + " -> " + kv.val); } - System.out.println("\nキーを個別に走査 Key"); + System.out.println("\nキー Key のみを走査"); for (int key : map.keySet()) { System.out.println(key); } - System.out.println("\n値を個別に走査 Value"); + System.out.println("\n値 Value のみを走査"); for (String val : map.valueSet()) { System.out.println(val); } } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_hashing/built_in_hash.java b/ja/codes/java/chapter_hashing/built_in_hash.java index 738c70b13..719d39b7c 100644 --- a/ja/codes/java/chapter_hashing/built_in_hash.java +++ b/ja/codes/java/chapter_hashing/built_in_hash.java @@ -13,26 +13,26 @@ public class built_in_hash { public static void main(String[] args) { int num = 3; int hashNum = Integer.hashCode(num); - System.out.println("整数 " + num + " のハッシュ値は " + hashNum + " です"); + System.out.println("整数 " + num + " のハッシュ値は " + hashNum); boolean bol = true; int hashBol = Boolean.hashCode(bol); - System.out.println("ブール値 " + bol + " のハッシュ値は " + hashBol + " です"); + System.out.println("真偽値 " + bol + " のハッシュ値は " + hashBol); double dec = 3.14159; int hashDec = Double.hashCode(dec); - System.out.println("小数 " + dec + " のハッシュ値は " + hashDec + " です"); + System.out.println("小数 " + dec + " のハッシュ値は " + hashDec); - String str = "Hello algorithm"; + String str = "Hello アルゴリズム"; int hashStr = str.hashCode(); - System.out.println("文字列 " + str + " のハッシュ値は " + hashStr + " です"); + System.out.println("文字列 " + str + " のハッシュ値は " + hashStr); - Object[] arr = { 12836, "Ha" }; + Object[] arr = { 12836, "シャオハー" }; int hashTup = Arrays.hashCode(arr); - System.out.println("配列 " + Arrays.toString(arr) + " のハッシュ値は " + hashTup + " です"); + System.out.println("配列 " + Arrays.toString(arr) + " のハッシュ値は " + hashTup); ListNode obj = new ListNode(0); int hashObj = obj.hashCode(); - System.out.println("ノードオブジェクト " + obj + " のハッシュ値は " + hashObj + " です"); + System.out.println("ノードオブジェクト " + obj + " のハッシュ値は " + hashObj); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_hashing/hash_map.java b/ja/codes/java/chapter_hashing/hash_map.java index 6411b5cb4..07ff2d719 100644 --- a/ja/codes/java/chapter_hashing/hash_map.java +++ b/ja/codes/java/chapter_hashing/hash_map.java @@ -15,38 +15,38 @@ public class hash_map { Map map = new HashMap<>(); /* 追加操作 */ - // ハッシュテーブルにキー値ペア (key, value) を追加 - map.put(12836, "Ha"); - map.put(15937, "Luo"); - map.put(16750, "Suan"); - map.put(13276, "Fa"); - map.put(10583, "Ya"); - System.out.println("\n追加後、ハッシュテーブルは\nKey -> Value"); + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.put(12836, "シャオハー"); + map.put(15937, "シャオルオ"); + map.put(16750, "シャオスワン"); + map.put(13276, "シャオファー"); + map.put(10583, "シャオヤー"); + System.out.println("\n追加後のハッシュ表は\nKey -> Value"); PrintUtil.printHashMap(map); /* 検索操作 */ - // ハッシュテーブルにキーを入力し、値を取得 + // キー key をハッシュテーブルに渡し、値 value を取得 String name = map.get(15937); - System.out.println("\n学生番号 15937 を入力し、名前 " + name + " を見つけました"); + System.out.println("\n学籍番号 15937 を入力すると、氏名 " + name); /* 削除操作 */ - // ハッシュテーブルからキー値ペア (key, value) を削除 + // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(10583); - System.out.println("\n10583 を削除後、ハッシュテーブルは\nKey -> Value"); + System.out.println("\n10583 を削除すると、ハッシュ表は\nKey -> Value"); PrintUtil.printHashMap(map); - /* ハッシュテーブルの走査 */ - System.out.println("\nキー値ペアを走査 Key->Value"); + /* ハッシュテーブルを走査 */ + System.out.println("\nキーと値の組 Key->Value を走査"); for (Map.Entry kv : map.entrySet()) { System.out.println(kv.getKey() + " -> " + kv.getValue()); } - System.out.println("\nキーを個別に走査 Key"); + System.out.println("\nキー Key のみを走査"); for (int key : map.keySet()) { System.out.println(key); } - System.out.println("\n値を個別に走査 Value"); + System.out.println("\n値 Value のみを走査"); for (String val : map.values()) { System.out.println(val); } } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_hashing/hash_map_chaining.java b/ja/codes/java/chapter_hashing/hash_map_chaining.java index 60113ee36..7b016ae67 100644 --- a/ja/codes/java/chapter_hashing/hash_map_chaining.java +++ b/ja/codes/java/chapter_hashing/hash_map_chaining.java @@ -11,9 +11,9 @@ import java.util.List; /* チェイン法ハッシュテーブル */ class HashMapChaining { - int size; // キー値ペアの数 - int capacity; // ハッシュテーブルの容量 - double loadThres; // 拡張をトリガーする負荷率の閾値 + int size; // キーと値のペア数 + int capacity; // ハッシュテーブル容量 + double loadThres; // リサイズを発動する負荷率のしきい値 int extendRatio; // 拡張倍率 List> buckets; // バケット配列 @@ -39,36 +39,36 @@ class HashMapChaining { return (double) size / capacity; } - /* クエリ操作 */ + /* 検索操作 */ String get(int key) { int index = hashFunc(key); List bucket = buckets.get(index); - // バケットを走査、キーが見つかった場合対応するvalを返す + // バケットを走査し、key が見つかれば対応する val を返す for (Pair pair : bucket) { if (pair.key == key) { return pair.val; } } - // キーが見つからない場合、nullを返す + // key が見つからない場合は null を返す return null; } /* 追加操作 */ void put(int key, String val) { - // 負荷率が閾値を超えた場合、拡張を実行 + // 負荷率がしきい値を超えたら、リサイズを実行 if (loadFactor() > loadThres) { extend(); } int index = hashFunc(key); List bucket = buckets.get(index); - // バケットを走査、指定したキーに遭遇した場合、対応するvalを更新して戻る + // バケットを走査し、指定した key が見つかれば対応する val を更新して返す for (Pair pair : bucket) { if (pair.key == key) { pair.val = val; return; } } - // キーが見つからない場合、キー値ペアを末尾に追加 + // その key が存在しなければ、キーと値のペアを末尾に追加 Pair pair = new Pair(key, val); bucket.add(pair); size++; @@ -78,7 +78,7 @@ class HashMapChaining { void remove(int key) { int index = hashFunc(key); List bucket = buckets.get(index); - // バケットを走査、その中からキー値ペアを削除 + // バケットを走査してキーと値のペアを削除 for (Pair pair : bucket) { if (pair.key == key) { bucket.remove(pair); @@ -90,16 +90,16 @@ class HashMapChaining { /* ハッシュテーブルを拡張 */ void extend() { - // 元のハッシュテーブルを一時的に保存 + // 元のハッシュテーブルを一時保存 List> bucketsTmp = buckets; - // 拡張された新しいハッシュテーブルを初期化 + // リサイズ後の新しいハッシュテーブルを初期化 capacity *= extendRatio; buckets = new ArrayList<>(capacity); for (int i = 0; i < capacity; i++) { buckets.add(new ArrayList<>()); } size = 0; - // 元のハッシュテーブルから新しいハッシュテーブルにキー値ペアを移動 + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for (List bucket : bucketsTmp) { for (Pair pair : bucket) { put(pair.key, pair.val); @@ -107,7 +107,7 @@ class HashMapChaining { } } - /* ハッシュテーブルを印刷 */ + /* ハッシュテーブルを出力 */ void print() { for (List bucket : buckets) { List res = new ArrayList<>(); @@ -125,24 +125,24 @@ public class hash_map_chaining { HashMapChaining map = new HashMapChaining(); /* 追加操作 */ - // ハッシュテーブルにキー値ペア (key, value) を追加 - map.put(12836, "Ha"); - map.put(15937, "Luo"); - map.put(16750, "Suan"); - map.put(13276, "Fa"); - map.put(10583, "Ya"); - System.out.println("\n追加後のハッシュテーブル\nKey -> Value"); + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.put(12836, "シャオハー"); + map.put(15937, "シャオルオ"); + map.put(16750, "シャオスワン"); + map.put(13276, "シャオファー"); + map.put(10583, "シャオヤー"); + System.out.println("\n追加後のハッシュ表は\nKey -> Value"); map.print(); - /* クエリ操作 */ - // ハッシュテーブルにキーを入力して値を取得 + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 String name = map.get(13276); - System.out.println("\n学生ID 13276を入力、名前 " + name + " を見つけました"); + System.out.println("\n学籍番号 13276 を入力すると、氏名 " + name); /* 削除操作 */ - // ハッシュテーブルからキー値ペア (key, value) を削除 + // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(12836); - System.out.println("\n12836を削除後のハッシュテーブル\nKey -> Value"); + System.out.println("\n12836 を削除すると、ハッシュ表は\nKey -> Value"); map.print(); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_hashing/hash_map_open_addressing.java b/ja/codes/java/chapter_hashing/hash_map_open_addressing.java index 2c7c64791..3519b492d 100644 --- a/ja/codes/java/chapter_hashing/hash_map_open_addressing.java +++ b/ja/codes/java/chapter_hashing/hash_map_open_addressing.java @@ -8,12 +8,12 @@ package chapter_hashing; /* オープンアドレス法ハッシュテーブル */ class HashMapOpenAddressing { - private int size; // キー値ペアの数 - private int capacity = 4; // ハッシュテーブルの容量 - private final double loadThres = 2.0 / 3.0; // 拡張をトリガーする負荷率の閾値 + private int size; // キーと値のペア数 + private int capacity = 4; // ハッシュテーブル容量 + private final double loadThres = 2.0 / 3.0; // リサイズを発動する負荷率のしきい値 private final int extendRatio = 2; // 拡張倍率 private Pair[] buckets; // バケット配列 - private final Pair TOMBSTONE = new Pair(-1, "-1"); // 削除マーク + private final Pair TOMBSTONE = new Pair(-1, "-1"); // 削除済みマーク /* コンストラクタ */ public HashMapOpenAddressing() { @@ -31,68 +31,68 @@ class HashMapOpenAddressing { return (double) size / capacity; } - /* keyに対応するバケットインデックスを検索 */ + /* key に対応するバケットインデックスを探す */ private int findBucket(int key) { int index = hashFunc(key); int firstTombstone = -1; - // 線形探査、空のバケットに遭遇したら終了 + // 線形プロービングを行い、空バケットに達したら終了 while (buckets[index] != null) { - // keyに遭遇した場合、対応するバケットインデックスを返す + // key が見つかったら、対応するバケットのインデックスを返す if (buckets[index].key == key) { - // 以前に削除マークに遭遇していた場合、キー値ペアをそのインデックスに移動 + // 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 if (firstTombstone != -1) { buckets[firstTombstone] = buckets[index]; buckets[index] = TOMBSTONE; return firstTombstone; // 移動後のバケットインデックスを返す } - return index; // バケットインデックスを返す + return index; // バケットのインデックスを返す } - // 最初に遭遇した削除マークを記録 + // 最初に見つかった削除マークを記録 if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { firstTombstone = index; } - // バケットインデックスを計算、末尾を超えた場合は先頭に戻る + // バケットのインデックスを計算し、末尾を越えたら先頭に戻る index = (index + 1) % capacity; } - // keyが存在しない場合、挿入ポイントのインデックスを返す + // key が存在しない場合は追加位置のインデックスを返す return firstTombstone == -1 ? index : firstTombstone; } - /* クエリ操作 */ + /* 検索操作 */ public String get(int key) { - // keyに対応するバケットインデックスを検索 + // key に対応するバケットインデックスを探す int index = findBucket(key); - // キー値ペアが見つかった場合、対応するvalを返す + // キーと値の組が見つかったら、対応する val を返す if (buckets[index] != null && buckets[index] != TOMBSTONE) { return buckets[index].val; } - // キー値ペアが存在しない場合、nullを返す + // キーと値の組が存在しなければ null を返す return null; } /* 追加操作 */ public void put(int key, String val) { - // 負荷率が閾値を超えた場合、拡張を実行 + // 負荷率がしきい値を超えたら、リサイズを実行 if (loadFactor() > loadThres) { extend(); } - // keyに対応するバケットインデックスを検索 + // key に対応するバケットインデックスを探す int index = findBucket(key); - // キー値ペアが見つかった場合、valを上書きして戻る + // キーと値の組が見つかったら、val を上書きして返す if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index].val = val; return; } - // キー値ペアが存在しない場合、キー値ペアを追加 + // キーと値の組が存在しない場合は、その組を追加する buckets[index] = new Pair(key, val); size++; } /* 削除操作 */ public void remove(int key) { - // keyに対応するバケットインデックスを検索 + // key に対応するバケットインデックスを探す int index = findBucket(key); - // キー値ペアが見つかった場合、削除マークで覆う + // キーと値の組が見つかったら、削除マーカーで上書きする if (buckets[index] != null && buckets[index] != TOMBSTONE) { buckets[index] = TOMBSTONE; size--; @@ -101,13 +101,13 @@ class HashMapOpenAddressing { /* ハッシュテーブルを拡張 */ private void extend() { - // 元のハッシュテーブルを一時的に保存 + // 元のハッシュテーブルを一時保存 Pair[] bucketsTmp = buckets; - // 拡張された新しいハッシュテーブルを初期化 + // リサイズ後の新しいハッシュテーブルを初期化 capacity *= extendRatio; buckets = new Pair[capacity]; size = 0; - // 元のハッシュテーブルから新しいハッシュテーブルにキー値ペアを移動 + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for (Pair pair : bucketsTmp) { if (pair != null && pair != TOMBSTONE) { put(pair.key, pair.val); @@ -115,7 +115,7 @@ class HashMapOpenAddressing { } } - /* ハッシュテーブルを印刷 */ + /* ハッシュテーブルを出力 */ public void print() { for (Pair pair : buckets) { if (pair == null) { @@ -135,24 +135,24 @@ public class hash_map_open_addressing { HashMapOpenAddressing hashmap = new HashMapOpenAddressing(); // 追加操作 - // ハッシュテーブルにキー値ペア (key, val) を追加 - hashmap.put(12836, "Ha"); - hashmap.put(15937, "Luo"); - hashmap.put(16750, "Suan"); - hashmap.put(13276, "Fa"); - hashmap.put(10583, "Ya"); - System.out.println("\n追加後のハッシュテーブル\nKey -> Value"); + // ハッシュテーブルにキーと値の組 (key, val) を追加する + hashmap.put(12836, "シャオハー"); + hashmap.put(15937, "シャオルオ"); + hashmap.put(16750, "シャオスワン"); + hashmap.put(13276, "シャオファー"); + hashmap.put(10583, "シャオヤー"); + System.out.println("\n追加後のハッシュ表は\nKey -> Value"); hashmap.print(); - // クエリ操作 - // ハッシュテーブルにキーを入力して値valを取得 + // 検索操作 + // ハッシュテーブルにキー key を入力し、値 val を得る String name = hashmap.get(13276); - System.out.println("\n学生ID 13276を入力、名前 " + name + " を見つけました"); + System.out.println("\n学籍番号 13276 を入力すると、氏名 " + name); // 削除操作 - // ハッシュテーブルからキー値ペア (key, val) を削除 + // ハッシュテーブルからキーと値の組 (key, val) を削除する hashmap.remove(16750); - System.out.println("\n16750を削除後のハッシュテーブル\nKey -> Value"); + System.out.println("\n16750 を削除すると、ハッシュ表は\nKey -> Value"); hashmap.print(); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_hashing/simple_hash.java b/ja/codes/java/chapter_hashing/simple_hash.java index d83823655..53ef2760b 100644 --- a/ja/codes/java/chapter_hashing/simple_hash.java +++ b/ja/codes/java/chapter_hashing/simple_hash.java @@ -27,7 +27,7 @@ public class simple_hash { return (int) hash; } - /* XORハッシュ */ + /* XOR ハッシュ */ static int xorHash(String key) { int hash = 0; final int MODULUS = 1000000007; @@ -48,18 +48,18 @@ public class simple_hash { } public static void main(String[] args) { - String key = "Hello algorithm"; + String key = "Hello アルゴリズム"; int hash = addHash(key); - System.out.println("加算ハッシュ値は " + hash + " です"); + System.out.println("加算ハッシュ値は " + hash); hash = mulHash(key); - System.out.println("乗算ハッシュ値は " + hash + " です"); + System.out.println("乗算ハッシュ値は " + hash); hash = xorHash(key); - System.out.println("XORハッシュ値は " + hash + " です"); + System.out.println("XOR ハッシュ値は " + hash); hash = rotHash(key); - System.out.println("回転ハッシュ値は " + hash + " です"); + System.out.println("回転ハッシュ値は " + hash); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_heap/heap.java b/ja/codes/java/chapter_heap/heap.java index 25b2a0bdc..5fd7b9ac3 100644 --- a/ja/codes/java/chapter_heap/heap.java +++ b/ja/codes/java/chapter_heap/heap.java @@ -11,14 +11,14 @@ import java.util.*; public class heap { public static void testPush(Queue heap, int val) { - heap.offer(val); // 要素をヒープにプッシュ - System.out.format("\n要素 %d をヒープに追加後\n", val); + heap.offer(val); // 要素をヒープに追加 + System.out.format("\n要素 %d をヒープに追加した後\n", val); PrintUtil.printHeap(heap); } public static void testPop(Queue heap) { - int val = heap.poll(); // ヒープの先頭要素をポップ - System.out.format("\n先頭要素 %d をヒープから削除後\n", val); + int val = heap.poll(); // ヒープ頂点の要素を取り出す + System.out.format("\nヒープトップ要素 %d を取り出した後\n", val); PrintUtil.printHeap(heap); } @@ -26,23 +26,23 @@ public class heap { /* ヒープを初期化 */ // 最小ヒープを初期化 Queue minHeap = new PriorityQueue<>(); - // 最大ヒープを初期化(必要に応じてラムダ式を使用してComparatorを変更) + // 最大ヒープを初期化する(lambda 式で Comparator を変更すればよい) Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); - System.out.println("\n以下のテストケースは最大ヒープ用です"); + System.out.println("\n以下のテストケースは最大ヒープ"); - /* 要素をヒープにプッシュ */ + /* 要素をヒープに追加 */ testPush(maxHeap, 1); testPush(maxHeap, 3); testPush(maxHeap, 2); testPush(maxHeap, 5); testPush(maxHeap, 4); - /* ヒープの先頭要素にアクセス */ + /* ヒープ頂点の要素を取得 */ int peek = maxHeap.peek(); - System.out.format("\nヒープの先頭要素は %d\n", peek); + System.out.format("\nヒープトップ要素は %d\n", peek); - /* ヒープの先頭要素をポップ */ + /* ヒープ頂点の要素を取り出す */ testPop(maxHeap); testPop(maxHeap); testPop(maxHeap); @@ -55,12 +55,12 @@ public class heap { /* ヒープが空かどうかを判定 */ boolean isEmpty = maxHeap.isEmpty(); - System.out.format("\nヒープは空ですか %b\n", isEmpty); + System.out.format("\nヒープが空かどうかは %b\n", isEmpty); /* リストを入力してヒープを構築 */ - // 時間計算量は O(n)、O(nlogn) ではない + // 時間計算量は O(n) であり、O(nlogn) ではない minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); - System.out.println("\nリストを入力して最小ヒープを構築"); + System.out.println("\nリストを入力して最小ヒープを構築した後"); PrintUtil.printHeap(minHeap); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_heap/my_heap.java b/ja/codes/java/chapter_heap/my_heap.java index 21fd13c08..ac5d6bc04 100644 --- a/ja/codes/java/chapter_heap/my_heap.java +++ b/ja/codes/java/chapter_heap/my_heap.java @@ -11,32 +11,32 @@ import java.util.*; /* 最大ヒープ */ class MaxHeap { - // リサイズの必要性を避けるため、配列の代わりにリストを使用 + // 配列ではなくリストを使うことで、拡張を考慮する必要がない private List maxHeap; - /* コンストラクタ、入力リストに基づいてヒープを構築 */ + /* コンストラクタ。入力リストに基づいてヒープを構築する */ public MaxHeap(List nums) { - // すべてのリスト要素をヒープに追加 + // リスト要素をそのままヒープに追加 maxHeap = new ArrayList<>(nums); - // 葉を除くすべてのノードをヒープ化 + // 葉ノード以外のすべてのノードをヒープ化 for (int i = parent(size() - 1); i >= 0; i--) { siftDown(i); } } - /* 左の子ノードのインデックスを取得 */ + /* 左子ノードのインデックスを取得 */ private int left(int i) { return 2 * i + 1; } - /* 右の子ノードのインデックスを取得 */ + /* 右子ノードのインデックスを取得 */ private int right(int i) { return 2 * i + 2; } /* 親ノードのインデックスを取得 */ private int parent(int i) { - return (i - 1) / 2; // 整数除算で切り下げ + return (i - 1) / 2; // 切り捨て除算 } /* 要素を交換 */ @@ -56,12 +56,12 @@ class MaxHeap { return size() == 0; } - /* ヒープの先頭要素にアクセス */ + /* ヒープ先頭要素にアクセス */ public int peek() { return maxHeap.get(0); } - /* 要素をヒープにプッシュ */ + /* 要素をヒープに追加 */ public void push(int val) { // ノードを追加 maxHeap.add(val); @@ -69,51 +69,51 @@ class MaxHeap { siftUp(size() - 1); } - /* ノード i から上向きにヒープ化を開始 */ + /* ノード i から始めて、下から上へヒープ化 */ private void siftUp(int i) { while (true) { // ノード i の親ノードを取得 int p = parent(i); - // 「根ノードを越える」または「ノードが修復不要」の場合、ヒープ化を終了 + // 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 if (p < 0 || maxHeap.get(i) <= maxHeap.get(p)) break; - // 2つのノードを交換 + // 2 つのノードを交換 swap(i, p); - // 上向きにヒープ化をループ + // ループで下から上へヒープ化 i = p; } } - /* 要素がヒープから退出 */ + /* 要素をヒープから取り出す */ public int pop() { - // 空の処理 + // 空判定の処理 if (isEmpty()) throw new IndexOutOfBoundsException(); - // 根ノードを最も右の葉ノードと交換(最初の要素を最後の要素と交換) + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) swap(0, size() - 1); // ノードを削除 int val = maxHeap.remove(size() - 1); // 上から下へヒープ化 siftDown(0); - // ヒープの先頭要素を返す + // ヒープ先頭要素を返す return val; } - /* ノード i から下向きにヒープ化を開始 */ + /* ノード i から始めて、上から下へヒープ化 */ private void siftDown(int i) { while (true) { - // i、l、r の中で最大のノードを決定し、ma とする + // ノード i, l, r のうち値が最大のノードを ma とする int l = left(i), r = right(i), ma = i; if (l < size() && maxHeap.get(l) > maxHeap.get(ma)) ma = l; if (r < size() && maxHeap.get(r) > maxHeap.get(ma)) ma = r; - // ノード i が最大の場合、またはインデックス l、r が範囲外の場合、さらなるヒープ化は不要、終了 + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if (ma == i) break; - // 2つのノードを交換 + // 2 つのノードを交換 swap(i, ma); - // 下向きにヒープ化をループ + // ループで上から下へヒープ化 i = ma; } } @@ -130,22 +130,22 @@ public class my_heap { public static void main(String[] args) { /* 最大ヒープを初期化 */ MaxHeap maxHeap = new MaxHeap(Arrays.asList(9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2)); - System.out.println("\nリストを入力してヒープを構築"); + System.out.println("\nリストを入力してヒープを構築した後"); maxHeap.print(); - /* ヒープの先頭要素にアクセス */ + /* ヒープ頂点の要素を取得 */ int peek = maxHeap.peek(); - System.out.format("\nヒープの先頭要素は %d\n", peek); + System.out.format("\nヒープトップ要素は %d\n", peek); - /* 要素をヒープにプッシュ */ + /* 要素をヒープに追加 */ int val = 7; maxHeap.push(val); - System.out.format("\n要素 %d をヒープに追加後\n", val); + System.out.format("\n要素 %d をヒープに追加した後\n", val); maxHeap.print(); - /* ヒープの先頭要素をポップ */ + /* ヒープ頂点の要素を取り出す */ peek = maxHeap.pop(); - System.out.format("\n先頭要素 %d をヒープから削除後\n", peek); + System.out.format("\nヒープトップ要素 %d を取り出した後\n", peek); maxHeap.print(); /* ヒープのサイズを取得 */ @@ -154,6 +154,6 @@ public class my_heap { /* ヒープが空かどうかを判定 */ boolean isEmpty = maxHeap.isEmpty(); - System.out.format("\nヒープは空ですか %b\n", isEmpty); + System.out.format("\nヒープが空かどうかは %b\n", isEmpty); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_heap/top_k.java b/ja/codes/java/chapter_heap/top_k.java index 749d836f7..52750b022 100644 --- a/ja/codes/java/chapter_heap/top_k.java +++ b/ja/codes/java/chapter_heap/top_k.java @@ -10,17 +10,17 @@ import utils.*; import java.util.*; public class top_k { - /* ヒープを使用して配列内の最大 k 個の要素を検索 */ + /* ヒープに基づいて配列中の最大の k 個の要素を探す */ static Queue topKHeap(int[] nums, int k) { // 最小ヒープを初期化 Queue heap = new PriorityQueue(); - // 配列の最初の k 個の要素をヒープに入力 + // 配列の先頭 k 個の要素をヒープに追加 for (int i = 0; i < k; i++) { heap.offer(nums[i]); } - // k+1 番目の要素から、ヒープの長さを k に保つ + // k+1 番目の要素から開始し、ヒープ長を k に保つ for (int i = k; i < nums.length; i++) { - // 現在の要素がヒープの先頭要素より大きい場合、ヒープの先頭要素を削除し、現在の要素をヒープに入力 + // 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する if (nums[i] > heap.peek()) { heap.poll(); heap.offer(nums[i]); @@ -34,7 +34,7 @@ public class top_k { int k = 3; Queue res = topKHeap(nums, k); - System.out.println("最大 " + k + " 個の要素は"); + System.out.println("最大の " + k + " 個の要素は"); PrintUtil.printHeap(res); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_searching/binary_search.java b/ja/codes/java/chapter_searching/binary_search.java index 3615ffe1e..e6341367b 100644 --- a/ja/codes/java/chapter_searching/binary_search.java +++ b/ja/codes/java/chapter_searching/binary_search.java @@ -7,39 +7,39 @@ package chapter_searching; public class binary_search { - /* 二分探索(両端閉区間) */ + /* 二分探索(両閉区間) */ static int binarySearch(int[] nums, int target) { - // 両端閉区間 [0, n-1] を初期化、すなわち i, j はそれぞれ配列の最初の要素と最後の要素を指す + // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す int i = 0, j = nums.length - 1; - // 探索区間が空になるまでループ(i > j のとき空) + // ループし、探索区間が空になったら終了する(i > j で空) while (i <= j) { int m = i + (j - i) / 2; // 中点インデックス m を計算 - if (nums[m] < target) // この状況は target が区間 [m+1, j] にあることを示す + if (nums[m] < target) // この場合、target は区間 [m+1, j] にある i = m + 1; - else if (nums[m] > target) // この状況は target が区間 [i, m-1] にあることを示す + else if (nums[m] > target) // この場合、target は区間 [i, m-1] にある j = m - 1; - else // 目標要素を見つけたので、そのインデックスを返す + else // 目標要素が見つかったらそのインデックスを返す return m; } - // 目標要素を見つけられなかったので、-1 を返す + // 目標要素が見つからなければ -1 を返す return -1; } /* 二分探索(左閉右開区間) */ static int binarySearchLCRO(int[] nums, int target) { - // 左閉右開区間 [0, n) を初期化、すなわち i, j はそれぞれ配列の最初の要素と最後の要素+1を指す + // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す int i = 0, j = nums.length; - // 探索区間が空になるまでループ(i = j のとき空) + // ループし、探索区間が空になったら終了する(i = j で空) while (i < j) { int m = i + (j - i) / 2; // 中点インデックス m を計算 - if (nums[m] < target) // この状況は target が区間 [m+1, j) にあることを示す + if (nums[m] < target) // この場合、target は区間 [m+1, j) にある i = m + 1; - else if (nums[m] > target) // この状況は target が区間 [i, m) にあることを示す + else if (nums[m] > target) // この場合、target は区間 [i, m) にある j = m; - else // 目標要素を見つけたので、そのインデックスを返す + else // 目標要素が見つかったらそのインデックスを返す return m; } - // 目標要素を見つけられなかったので、-1 を返す + // 目標要素が見つからなければ -1 を返す return -1; } @@ -47,12 +47,12 @@ public class binary_search { int target = 6; int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }; - /* 二分探索(両端閉区間) */ + /* 二分探索(両閉区間) */ int index = binarySearch(nums, target); - System.out.println("目標要素 6 のインデックス = " + index); + System.out.println("対象要素 6 のインデックス = " + index); /* 二分探索(左閉右開区間) */ index = binarySearchLCRO(nums, target); - System.out.println("目標要素 6 のインデックス = " + index); + System.out.println("対象要素 6 のインデックス = " + index); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_searching/binary_search_edge.java b/ja/codes/java/chapter_searching/binary_search_edge.java index 5a13abd31..1fd44a386 100644 --- a/ja/codes/java/chapter_searching/binary_search_edge.java +++ b/ja/codes/java/chapter_searching/binary_search_edge.java @@ -9,27 +9,27 @@ package chapter_searching; public class binary_search_edge { /* 最も左の target を二分探索 */ static int binarySearchLeftEdge(int[] nums, int target) { - // target の挿入点を見つけることと等価 + // target の挿入位置を探すのと等価 int i = binary_search_insertion.binarySearchInsertion(nums, target); - // target を見つけられなかったので、-1 を返す + // target が見つからなければ、-1 を返す if (i == nums.length || nums[i] != target) { return -1; } - // target を見つけたので、インデックス i を返す + // target が見つかったら、インデックス i を返す return i; } /* 最も右の target を二分探索 */ static int binarySearchRightEdge(int[] nums, int target) { - // 最も左の target + 1 を見つけることに変換 + // 最左の target + 1 を探す問題に変換する int i = binary_search_insertion.binarySearchInsertion(nums, target + 1); // j は最も右の target を指し、i は target より大きい最初の要素を指す int j = i - 1; - // target を見つけられなかったので、-1 を返す + // target が見つからなければ、-1 を返す if (j == -1 || nums[j] != target) { return -1; } - // target を見つけたので、インデックス j を返す + // target が見つかったら、インデックス j を返す return j; } @@ -38,12 +38,12 @@ public class binary_search_edge { int[] nums = { 1, 3, 6, 6, 6, 6, 6, 10, 12, 15 }; System.out.println("\n配列 nums = " + java.util.Arrays.toString(nums)); - // 左右の境界を二分探索 + // 二分探索で左端と右端を探す for (int target : new int[] { 6, 7 }) { int index = binarySearchLeftEdge(nums, target); - System.out.println("要素 " + target + " の最も左のインデックスは " + index); + System.out.println("一番左の要素 " + target + " のインデックスは " + index); index = binarySearchRightEdge(nums, target); - System.out.println("要素 " + target + " の最も右のインデックスは " + index); + System.out.println("一番右の要素 " + target + " のインデックスは " + index); } } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_searching/binary_search_insertion.java b/ja/codes/java/chapter_searching/binary_search_insertion.java index 5f78fb4ac..3f5a00da9 100644 --- a/ja/codes/java/chapter_searching/binary_search_insertion.java +++ b/ja/codes/java/chapter_searching/binary_search_insertion.java @@ -7,9 +7,9 @@ package chapter_searching; class binary_search_insertion { - /* 挿入点の二分探索(重複要素なし) */ + /* 二分探索で挿入位置を探す(重複要素なし) */ static int binarySearchInsertionSimple(int[] nums, int target) { - int i = 0, j = nums.length - 1; // 両端閉区間 [0, n-1] を初期化 + int i = 0, j = nums.length - 1; // 両閉区間 [0, n-1] を初期化 while (i <= j) { int m = i + (j - i) / 2; // 中点インデックス m を計算 if (nums[m] < target) { @@ -17,16 +17,16 @@ class binary_search_insertion { } else if (nums[m] > target) { j = m - 1; // target は区間 [i, m-1] にある } else { - return m; // target を見つけたので、挿入点 m を返す + return m; // target が見つかったら、挿入位置 m を返す } } - // target を見つけられなかったので、挿入点 i を返す + // target が見つからなければ、挿入位置 i を返す return i; } - /* 挿入点の二分探索(重複要素あり) */ + /* 二分探索で挿入位置を探す(重複要素あり) */ static int binarySearchInsertion(int[] nums, int target) { - int i = 0, j = nums.length - 1; // 両端閉区間 [0, n-1] を初期化 + int i = 0, j = nums.length - 1; // 両閉区間 [0, n-1] を初期化 while (i <= j) { int m = i + (j - i) / 2; // 中点インデックス m を計算 if (nums[m] < target) { @@ -37,7 +37,7 @@ class binary_search_insertion { j = m - 1; // target より小さい最初の要素は区間 [i, m-1] にある } } - // 挿入点 i を返す + // 挿入位置 i を返す return i; } @@ -45,19 +45,19 @@ class binary_search_insertion { // 重複要素のない配列 int[] nums = { 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }; System.out.println("\n配列 nums = " + java.util.Arrays.toString(nums)); - // 挿入点の二分探索 + // 二分探索で挿入位置を探す for (int target : new int[] { 6, 9 }) { int index = binarySearchInsertionSimple(nums, target); - System.out.println("要素 " + target + " の挿入点インデックスは " + index); + System.out.println("要素 " + target + " の挿入位置のインデックスは " + index); } - // 重複要素のある配列 + // 重複要素を含む配列 nums = new int[] { 1, 3, 6, 6, 6, 6, 6, 10, 12, 15 }; System.out.println("\n配列 nums = " + java.util.Arrays.toString(nums)); - // 挿入点の二分探索 + // 二分探索で挿入位置を探す for (int target : new int[] { 2, 6, 20 }) { int index = binarySearchInsertion(nums, target); - System.out.println("要素 " + target + " の挿入点インデックスは " + index); + System.out.println("要素 " + target + " の挿入位置のインデックスは " + index); } } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_searching/hashing_search.java b/ja/codes/java/chapter_searching/hashing_search.java index c31fc10f2..4210f9674 100644 --- a/ja/codes/java/chapter_searching/hashing_search.java +++ b/ja/codes/java/chapter_searching/hashing_search.java @@ -12,15 +12,15 @@ import java.util.*; public class hashing_search { /* ハッシュ探索(配列) */ static int hashingSearchArray(Map map, int target) { - // ハッシュテーブルのキー: 目標要素、値: インデックス - // ハッシュテーブルにこのキーが含まれていない場合、-1 を返す + // ハッシュテーブルの key: 目標要素、value: インデックス + // ハッシュテーブルにこの key がなければ -1 を返す return map.getOrDefault(target, -1); } /* ハッシュ探索(連結リスト) */ static ListNode hashingSearchLinkedList(Map map, int target) { - // ハッシュテーブルのキー: 目標ノードの値、値: ノードオブジェクト - // キーがハッシュテーブルにない場合、null を返す + // ハッシュテーブルの key: 目標ノード値、value: ノードオブジェクト + // ハッシュテーブルにこの key がなければ null を返す return map.getOrDefault(target, null); } @@ -32,20 +32,20 @@ public class hashing_search { // ハッシュテーブルを初期化 Map map = new HashMap<>(); for (int i = 0; i < nums.length; i++) { - map.put(nums[i], i); // キー: 要素、値: インデックス + map.put(nums[i], i); // key: 要素、value: インデックス } int index = hashingSearchArray(map, target); - System.out.println("目標要素 3 のインデックスは " + index); + System.out.println("対象要素 3 のインデックス = " + index); /* ハッシュ探索(連結リスト) */ ListNode head = ListNode.arrToLinkedList(nums); // ハッシュテーブルを初期化 Map map1 = new HashMap<>(); while (head != null) { - map1.put(head.val, head); // キー: ノードの値、値: ノード + map1.put(head.val, head); // key: ノード値、value: ノード head = head.next; } ListNode node = hashingSearchLinkedList(map1, target); - System.out.println("目標ノード値 3 に対応するノードオブジェクトは " + node); + System.out.println("対象ノード値 3 に対応するノードオブジェクトは " + node); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_searching/linear_search.java b/ja/codes/java/chapter_searching/linear_search.java index 3ad624aec..39f9f9532 100644 --- a/ja/codes/java/chapter_searching/linear_search.java +++ b/ja/codes/java/chapter_searching/linear_search.java @@ -13,38 +13,38 @@ public class linear_search { static int linearSearchArray(int[] nums, int target) { // 配列を走査 for (int i = 0; i < nums.length; i++) { - // 目標要素を見つけたので、そのインデックスを返す + // 目標要素が見つかったらそのインデックスを返す if (nums[i] == target) return i; } - // 目標要素を見つけられなかったので、-1 を返す + // 目標要素が見つからなければ -1 を返す return -1; } /* 線形探索(連結リスト) */ static ListNode linearSearchLinkedList(ListNode head, int target) { - // リストを走査 + // 連結リストを走査 while (head != null) { - // 目標ノードを見つけたので、それを返す + // 対象ノードが見つかったら、それを返す if (head.val == target) return head; head = head.next; } - // 目標ノードが見つからない場合、null を返す + // 対象ノードが見つからない場合は null を返す return null; } public static void main(String[] args) { int target = 3; - /* 配列で線形探索を実行 */ + /* 配列で線形探索を行う */ int[] nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; int index = linearSearchArray(nums, target); - System.out.println("目標要素 3 のインデックスは " + index); + System.out.println("対象要素 3 のインデックス = " + index); - /* 連結リストで線形探索を実行 */ + /* 連結リストで線形探索を行う */ ListNode head = ListNode.arrToLinkedList(nums); ListNode node = linearSearchLinkedList(head, target); - System.out.println("目標ノード値 3 に対応するノードオブジェクトは " + node); + System.out.println("対象ノード値 3 に対応するノードオブジェクトは " + node); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_searching/two_sum.java b/ja/codes/java/chapter_searching/two_sum.java index 2485d5f7f..0deb08d23 100644 --- a/ja/codes/java/chapter_searching/two_sum.java +++ b/ja/codes/java/chapter_searching/two_sum.java @@ -9,10 +9,10 @@ package chapter_searching; import java.util.*; public class two_sum { - /* 方法一: 暴力列挙 */ + /* 方法 1:総当たり列挙 */ static int[] twoSumBruteForce(int[] nums, int target) { int size = nums.length; - // 二重ループ、時間計算量は O(n^2) + // 2重ループのため、時間計算量は O(n^2) for (int i = 0; i < size - 1; i++) { for (int j = i + 1; j < size; j++) { if (nums[i] + nums[j] == target) @@ -22,12 +22,12 @@ public class two_sum { return new int[0]; } - /* 方法二: 補助ハッシュテーブル */ + /* 方法 2:補助ハッシュテーブル */ static int[] twoSumHashTable(int[] nums, int target) { int size = nums.length; - // 補助ハッシュテーブル、空間計算量は O(n) + // 補助ハッシュテーブルを使用し、空間計算量は O(n) Map dic = new HashMap<>(); - // 単一層ループ、時間計算量は O(n) + // 単一ループで、時間計算量は O(n) for (int i = 0; i < size; i++) { if (dic.containsKey(target - nums[i])) { return new int[] { dic.get(target - nums[i]), i }; @@ -38,16 +38,16 @@ public class two_sum { } public static void main(String[] args) { - // ======= テストケース ======= + // ======= Test Case ======= int[] nums = { 2, 7, 11, 15 }; int target = 13; - // ====== ドライバーコード ====== - // 方法一 + // ====== Driver Code ====== + // 方法 1 int[] res = twoSumBruteForce(nums, target); - System.out.println("方法一 res = " + Arrays.toString(res)); - // 方法二 + System.out.println("方法1 res = " + Arrays.toString(res)); + // 方法 2 res = twoSumHashTable(nums, target); - System.out.println("方法二 res = " + Arrays.toString(res)); + System.out.println("方法2 res = " + Arrays.toString(res)); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_sorting/bubble_sort.java b/ja/codes/java/chapter_sorting/bubble_sort.java index 4bb0fce85..9fd81b466 100644 --- a/ja/codes/java/chapter_sorting/bubble_sort.java +++ b/ja/codes/java/chapter_sorting/bubble_sort.java @@ -11,9 +11,9 @@ import java.util.*; public class bubble_sort { /* バブルソート */ static void bubbleSort(int[] nums) { - // 外側ループ: 未ソート範囲は [0, i] + // 外側のループ:未ソート区間は [0, i] for (int i = nums.length - 1; i > 0; i--) { - // 内側ループ: 未ソート範囲 [0, i] の最大要素を範囲の右端に交換 + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // nums[j] と nums[j + 1] を交換 @@ -25,33 +25,33 @@ public class bubble_sort { } } - /* バブルソート(フラグによる最適化) */ + /* バブルソート(フラグ最適化) */ static void bubbleSortWithFlag(int[] nums) { - // 外側ループ: 未ソート範囲は [0, i] + // 外側のループ:未ソート区間は [0, i] for (int i = nums.length - 1; i > 0; i--) { - boolean flag = false; // フラグを初期化 - // 内側ループ: 未ソート範囲 [0, i] の最大要素を範囲の右端に交換 + boolean flag = false; // フラグを初期化する + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { // nums[j] と nums[j + 1] を交換 int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; - flag = true; // 交換された要素を記録 + flag = true; // 交換する要素を記録 } } if (!flag) - break; // この「バブリング」ラウンドで要素が交換されなかった場合、終了 + break; // このバブル処理で要素交換が一度もなければそのまま終了 } } public static void main(String[] args) { int[] nums = { 4, 1, 3, 1, 5, 2 }; bubbleSort(nums); - System.out.println("バブルソート後、nums = " + Arrays.toString(nums)); + System.out.println("バブルソート完了後の nums = " + Arrays.toString(nums)); int[] nums1 = { 4, 1, 3, 1, 5, 2 }; bubbleSortWithFlag(nums1); - System.out.println("バブルソート後、nums1 = " + Arrays.toString(nums1)); + System.out.println("バブルソート完了後の nums1 = " + Arrays.toString(nums1)); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_sorting/bucket_sort.java b/ja/codes/java/chapter_sorting/bucket_sort.java index 7552fd245..1153d9ca2 100644 --- a/ja/codes/java/chapter_sorting/bucket_sort.java +++ b/ja/codes/java/chapter_sorting/bucket_sort.java @@ -11,25 +11,25 @@ import java.util.*; public class bucket_sort { /* バケットソート */ static void bucketSort(float[] nums) { - // k = n/2 個のバケットを初期化、各バケットに期待される要素数は 2 個 + // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする int k = nums.length / 2; List> buckets = new ArrayList<>(); for (int i = 0; i < k; i++) { buckets.add(new ArrayList<>()); } - // 1. 配列要素を各バケットに分散 + // 1. 配列要素を各バケットに振り分ける for (float num : nums) { - // 入力データ範囲は [0, 1)、num * k を使ってインデックス範囲 [0, k-1] にマッピング + // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する int i = (int) (num * k); // num をバケット i に追加 buckets.get(i).add(num); } - // 2. 各バケットをソート + // 2. 各バケットをソートする for (List bucket : buckets) { - // 組み込みソート関数を使用、他のソートアルゴリズムに置き換えることも可能 + // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい Collections.sort(bucket); } - // 3. バケットを走査して結果をマージ + // 3. バケットを走査して結果を結合 int i = 0; for (List bucket : buckets) { for (float num : bucket) { @@ -39,9 +39,9 @@ public class bucket_sort { } public static void main(String[] args) { - // 入力データが浮動小数点、範囲 [0, 1) と仮定 + // 入力データは範囲 [0, 1) の浮動小数点数とする float[] nums = { 0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f }; bucketSort(nums); - System.out.println("バケットソート後、nums = " + Arrays.toString(nums)); + System.out.println("バケットソート完了後の nums = " + Arrays.toString(nums)); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_sorting/counting_sort.java b/ja/codes/java/chapter_sorting/counting_sort.java index fdeb1fc29..dd84fc673 100644 --- a/ja/codes/java/chapter_sorting/counting_sort.java +++ b/ja/codes/java/chapter_sorting/counting_sort.java @@ -10,20 +10,20 @@ import java.util.*; public class counting_sort { /* 計数ソート */ - // 簡単な実装、オブジェクトのソートには使用できない + // 簡易実装のため、オブジェクトのソートには使えない static void countingSortNaive(int[] nums) { - // 1. 配列の最大要素 m を統計 + // 1. 配列の最大要素 m を求める int m = 0; for (int num : nums) { m = Math.max(m, num); } - // 2. 各数字の出現回数を統計 + // 2. 各数値の出現回数を数える // counter[num] は num の出現回数を表す int[] counter = new int[m + 1]; for (int num : nums) { counter[num]++; } - // 3. counter を走査し、各要素を元の配列 nums に戻す + // 3. counter を走査し、各要素を元の配列 nums に書き戻す int i = 0; for (int num = 0; num < m + 1; num++) { for (int j = 0; j < counter[num]; j++, i++) { @@ -33,34 +33,34 @@ public class counting_sort { } /* 計数ソート */ - // 完全な実装、オブジェクトをソートでき、安定ソート + // 完全な実装で、オブジェクトをソートでき、かつ安定ソートである static void countingSort(int[] nums) { - // 1. 配列の最大要素 m を統計 + // 1. 配列の最大要素 m を求める int m = 0; for (int num : nums) { m = Math.max(m, num); } - // 2. 各数字の出現回数を統計 + // 2. 各数値の出現回数を数える // counter[num] は num の出現回数を表す int[] counter = new int[m + 1]; for (int num : nums) { counter[num]++; } - // 3. counter の累積和を計算し、「出現回数」を「尻尾インデックス」に変換 - // counter[num]-1 は res 内で num が出現する最後のインデックス + // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する + // つまり counter[num]-1 は、num が res に最後に現れるインデックス for (int i = 0; i < m; i++) { counter[i + 1] += counter[i]; } - // 4. nums を逆順に走査し、各要素を結果配列 res に配置 - // 結果を記録する配列 res を初期化 + // 4. nums を逆順に走査し、各要素を結果配列 res に格納する + // 結果を記録するための配列 res を初期化 int n = nums.length; int[] res = new int[n]; for (int i = n - 1; i >= 0; i--) { int num = nums[i]; res[counter[num] - 1] = num; // num を対応するインデックスに配置 - counter[num]--; // 累積和を 1 減算し、num を配置する次のインデックスを取得 + counter[num]--; // 累積和を 1 減らして、次に num を配置するインデックスを得る } - // 結果配列 res を使って元の配列 nums を上書き + // 結果配列 res で元の配列 nums を上書きする for (int i = 0; i < n; i++) { nums[i] = res[i]; } @@ -69,10 +69,10 @@ public class counting_sort { public static void main(String[] args) { int[] nums = { 1, 0, 1, 2, 0, 4, 0, 2, 2, 4 }; countingSortNaive(nums); - System.out.println("計数ソート後(オブジェクトソート不可)、nums = " + Arrays.toString(nums)); + System.out.println("カウントソート(オブジェクトはソート不可)完了後の nums = " + Arrays.toString(nums)); int[] nums1 = { 1, 0, 1, 2, 0, 4, 0, 2, 2, 4 }; countingSort(nums1); - System.out.println("計数ソート後、nums1 = " + Arrays.toString(nums1)); + System.out.println("カウントソート完了後の nums1 = " + Arrays.toString(nums1)); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_sorting/heap_sort.java b/ja/codes/java/chapter_sorting/heap_sort.java index f6efa1966..ae188e377 100644 --- a/ja/codes/java/chapter_sorting/heap_sort.java +++ b/ja/codes/java/chapter_sorting/heap_sort.java @@ -9,10 +9,10 @@ package chapter_sorting; import java.util.Arrays; public class heap_sort { - /* ヒープの長さは n、ノード i から上から下へヒープ化開始 */ + /* ヒープの長さは n。ノード i から下方向にヒープ化 */ public static void siftDown(int[] nums, int n, int i) { while (true) { - // i, l, r の中で最大のノードを判定し、ma とする + // ノード i, l, r のうち値が最大のノードを ma とする int l = 2 * i + 1; int r = 2 * i + 2; int ma = i; @@ -20,31 +20,31 @@ public class heap_sort { ma = l; if (r < n && nums[r] > nums[ma]) ma = r; - // ノード i が最大、またはインデックス l, r が範囲外の場合、さらなるヒープ化は不要、ブレーク + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if (ma == i) break; - // 2つのノードを交換 + // 2 つのノードを交換 int temp = nums[i]; nums[i] = nums[ma]; nums[ma] = temp; - // 下向きにヒープ化をループ + // ループで上から下へヒープ化 i = ma; } } /* ヒープソート */ public static void heapSort(int[] nums) { - // ヒープ構築操作: 葉ノード以外のすべてのノードをヒープ化 + // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する for (int i = nums.length / 2 - 1; i >= 0; i--) { siftDown(nums, nums.length, i); } - // ヒープから最大要素を抽出し、n-1 回繰り返し + // ヒープから最大要素を取り出し、n-1 回繰り返す for (int i = nums.length - 1; i > 0; i--) { - // ルートノードと最も右の葉ノードを交換(最初の要素と最後の要素を交換) + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) int tmp = nums[0]; nums[0] = nums[i]; nums[i] = tmp; - // ルートノードから上から下へヒープ化開始 + // 根ノードを起点に、上から下へヒープ化 siftDown(nums, i, 0); } } @@ -52,6 +52,6 @@ public class heap_sort { public static void main(String[] args) { int[] nums = { 4, 1, 3, 1, 5, 2 }; heapSort(nums); - System.out.println("ヒープソート後、nums = " + Arrays.toString(nums)); + System.out.println("ヒープソート完了後の nums = " + Arrays.toString(nums)); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_sorting/insertion_sort.java b/ja/codes/java/chapter_sorting/insertion_sort.java index b54993f85..cf2e0d03c 100644 --- a/ja/codes/java/chapter_sorting/insertion_sort.java +++ b/ja/codes/java/chapter_sorting/insertion_sort.java @@ -11,21 +11,21 @@ import java.util.*; public class insertion_sort { /* 挿入ソート */ static void insertionSort(int[] nums) { - // 外側ループ: ソート済み範囲は [0, i-1] + // 外側ループ:整列済み区間は [0, i-1] for (int i = 1; i < nums.length; i++) { int base = nums[i], j = i - 1; - // 内側ループ: base をソート済み範囲 [0, i-1] の正しい位置に挿入 + // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する while (j >= 0 && nums[j] > base) { - nums[j + 1] = nums[j]; // nums[j] を右に1つ移動 + nums[j + 1] = nums[j]; // nums[j] を 1 つ右へ移動する j--; } - nums[j + 1] = base; // base を正しい位置に代入 + nums[j + 1] = base; // base を正しい位置に配置する } } public static void main(String[] args) { int[] nums = { 4, 1, 3, 1, 5, 2 }; insertionSort(nums); - System.out.println("挿入ソート後、nums = " + Arrays.toString(nums)); + System.out.println("挿入ソート完了後の nums = " + Arrays.toString(nums)); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_sorting/merge_sort.java b/ja/codes/java/chapter_sorting/merge_sort.java index 72aa5a772..cbafed3f5 100644 --- a/ja/codes/java/chapter_sorting/merge_sort.java +++ b/ja/codes/java/chapter_sorting/merge_sort.java @@ -11,26 +11,26 @@ import java.util.*; public class merge_sort { /* 左部分配列と右部分配列をマージ */ static void merge(int[] nums, int left, int mid, int right) { - // 左部分配列区間は [left, mid]、右部分配列区間は [mid+1, right] - // 一時配列 tmp を作成してマージ結果を格納 + // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] + // マージ結果を格納する一時配列 tmp を作成 int[] tmp = new int[right - left + 1]; - // 左右部分配列の開始インデックスを初期化 + // 左右の部分配列の開始インデックスを初期化する int i = left, j = mid + 1, k = 0; - // 両部分配列にまだ要素がある間、比較してより小さい要素を一時配列にコピー + // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする while (i <= mid && j <= right) { if (nums[i] <= nums[j]) tmp[k++] = nums[i++]; else tmp[k++] = nums[j++]; } - // 左右部分配列の残りの要素を一時配列にコピー + // 左右の部分配列の残り要素を一時配列にコピーする while (i <= mid) { tmp[k++] = nums[i++]; } while (j <= right) { tmp[k++] = nums[j++]; } - // 一時配列 tmp の要素を元の配列 nums の対応する区間にコピーバック + // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする for (k = 0; k < tmp.length; k++) { nums[left + k] = tmp[k]; } @@ -40,12 +40,12 @@ public class merge_sort { static void mergeSort(int[] nums, int left, int right) { // 終了条件 if (left >= right) - return; // 部分配列の長さが 1 のとき再帰を終了 - // 分割段階 + return; // 部分配列の長さが 1 になったら再帰を終了 + // 分割フェーズ int mid = left + (right - left) / 2; // 中点を計算 - mergeSort(nums, left, mid); // 左部分配列を再帰的に処理 - mergeSort(nums, mid + 1, right); // 右部分配列を再帰的に処理 - // マージ段階 + mergeSort(nums, left, mid); // 左部分配列を再帰処理 + mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理 + // マージフェーズ merge(nums, left, mid, right); } @@ -53,6 +53,6 @@ public class merge_sort { /* マージソート */ int[] nums = { 7, 3, 2, 6, 0, 1, 5, 4 }; mergeSort(nums, 0, nums.length - 1); - System.out.println("マージソート後、nums = " + Arrays.toString(nums)); + System.out.println("マージソート完了後の nums = " + Arrays.toString(nums)); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_sorting/quick_sort.java b/ja/codes/java/chapter_sorting/quick_sort.java index 4a688cd74..6c4733e05 100644 --- a/ja/codes/java/chapter_sorting/quick_sort.java +++ b/ja/codes/java/chapter_sorting/quick_sort.java @@ -10,51 +10,51 @@ import java.util.*; /* クイックソートクラス */ class QuickSort { - /* 要素を交換 */ + /* 要素の交換 */ static void swap(int[] nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } - /* 分割 */ + /* 番兵分割 */ static int partition(int[] nums, int left, int right) { - // nums[left] を基準値として使用 + // nums[left] を基準値とする int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) - j--; // 右から左へ、基準値より小さい最初の要素を検索 + j--; // 右から左へ基準値未満の最初の要素を探す while (i < j && nums[i] <= nums[left]) - i++; // 左から右へ、基準値より大きい最初の要素を検索 - swap(nums, i, j); // これら2つの要素を交換 + i++; // 左から右へ基準値より大きい最初の要素を探す + swap(nums, i, j); // この 2 つの要素を交換 } - swap(nums, i, left); // 基準値を2つの部分配列の境界に交換 + swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する return i; // 基準値のインデックスを返す } /* クイックソート */ public static void quickSort(int[] nums, int left, int right) { - // 部分配列の長さが 1 のとき再帰を終了 + // 部分配列の長さが 1 なら再帰を終了する if (left >= right) return; - // 分割 + // 番兵分割 int pivot = partition(nums, left, right); - // 左部分配列と右部分配列を再帰的に処理 + // 左右の部分配列を再帰処理 quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } } -/* クイックソートクラス(中央値基準最適化) */ +/* クイックソートクラス(中央値ピボット最適化) */ class QuickSortMedian { - /* 要素を交換 */ + /* 要素の交換 */ static void swap(int[] nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } - /* 3つの候補要素の中央値を選択 */ + /* 3つの候補要素の中央値を選ぶ */ static int medianThree(int[] nums, int left, int mid, int right) { int l = nums[left], m = nums[mid], r = nums[right]; if ((l <= m && m <= r) || (r <= m && m <= l)) @@ -64,75 +64,75 @@ class QuickSortMedian { return right; } - /* 分割(3つの中央値) */ + /* 番兵による分割処理(3 点中央値) */ static int partition(int[] nums, int left, int right) { - // 3つの候補要素の中央値を選択 + // 3つの候補要素の中央値を選ぶ int med = medianThree(nums, left, (left + right) / 2, right); - // 中央値を配列の最左端の位置に交換 + // 中央値を配列の最左端に交換する swap(nums, left, med); - // nums[left] を基準値として使用 + // nums[left] を基準値とする int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) - j--; // 右から左へ、基準値より小さい最初の要素を検索 + j--; // 右から左へ基準値未満の最初の要素を探す while (i < j && nums[i] <= nums[left]) - i++; // 左から右へ、基準値より大きい最初の要素を検索 - swap(nums, i, j); // これら2つの要素を交換 + i++; // 左から右へ基準値より大きい最初の要素を探す + swap(nums, i, j); // この 2 つの要素を交換 } - swap(nums, i, left); // 基準値を2つの部分配列の境界に交換 + swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する return i; // 基準値のインデックスを返す } /* クイックソート */ public static void quickSort(int[] nums, int left, int right) { - // 部分配列の長さが 1 のとき再帰を終了 + // 部分配列の長さが 1 なら再帰を終了する if (left >= right) return; - // 分割 + // 番兵分割 int pivot = partition(nums, left, right); - // 左部分配列と右部分配列を再帰的に処理 + // 左右の部分配列を再帰処理 quickSort(nums, left, pivot - 1); quickSort(nums, pivot + 1, right); } } -/* クイックソートクラス(末尾再帰最適化) */ +/* クイックソートクラス(再帰深度最適化) */ class QuickSortTailCall { - /* 要素を交換 */ + /* 要素の交換 */ static void swap(int[] nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } - /* 分割 */ + /* 番兵分割 */ static int partition(int[] nums, int left, int right) { - // nums[left] を基準値として使用 + // nums[left] を基準値とする int i = left, j = right; while (i < j) { while (i < j && nums[j] >= nums[left]) - j--; // 右から左へ、基準値より小さい最初の要素を検索 + j--; // 右から左へ基準値未満の最初の要素を探す while (i < j && nums[i] <= nums[left]) - i++; // 左から右へ、基準値より大きい最初の要素を検索 - swap(nums, i, j); // これら2つの要素を交換 + i++; // 左から右へ基準値より大きい最初の要素を探す + swap(nums, i, j); // この 2 つの要素を交換 } - swap(nums, i, left); // 基準値を2つの部分配列の境界に交換 + swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する return i; // 基準値のインデックスを返す } - /* クイックソート(末尾再帰最適化) */ + /* クイックソート(再帰深度最適化) */ public static void quickSort(int[] nums, int left, int right) { - // 部分配列の長さが 1 のとき終了 + // 部分配列の長さが 1 なら終了 while (left < right) { - // 分割操作 + // 番兵による分割処理 int pivot = partition(nums, left, right); - // 2つの部分配列のうち短い方にクイックソートを実行 + // 2 つの部分配列のうち短いほうにクイックソートを適用する if (pivot - left < right - pivot) { quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソート - left = pivot + 1; // 残りの未ソート区間は [pivot + 1, right] + left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right] } else { quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソート - right = pivot - 1; // 残りの未ソート区間は [left, pivot - 1] + right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1] } } } @@ -143,16 +143,16 @@ public class quick_sort { /* クイックソート */ int[] nums = { 2, 4, 1, 0, 3, 5 }; QuickSort.quickSort(nums, 0, nums.length - 1); - System.out.println("クイックソート後、nums = " + Arrays.toString(nums)); + System.out.println("クイックソート完了後の nums = " + Arrays.toString(nums)); - /* クイックソート(中央値基準最適化) */ + /* クイックソート(中央値の基準値で最適化) */ int[] nums1 = { 2, 4, 1, 0, 3, 5 }; QuickSortMedian.quickSort(nums1, 0, nums1.length - 1); - System.out.println("中央値基準最適化クイックソート後、nums1 = " + Arrays.toString(nums1)); + System.out.println("クイックソート(中央値ピボット最適化)完了後の nums1 = " + Arrays.toString(nums1)); - /* クイックソート(末尾再帰最適化) */ + /* クイックソート(再帰深度最適化) */ int[] nums2 = { 2, 4, 1, 0, 3, 5 }; QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1); - System.out.println("末尾再帰最適化クイックソート後、nums2 = " + Arrays.toString(nums2)); + System.out.println("クイックソート(再帰深度最適化)完了後の nums2 = " + Arrays.toString(nums2)); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_sorting/radix_sort.java b/ja/codes/java/chapter_sorting/radix_sort.java index 2de1eeac0..6a9df9ba4 100644 --- a/ja/codes/java/chapter_sorting/radix_sort.java +++ b/ja/codes/java/chapter_sorting/radix_sort.java @@ -9,52 +9,52 @@ package chapter_sorting; import java.util.*; public class radix_sort { - /* 要素 num の k 番目の桁を取得、exp = 10^(k-1) */ + /* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */ static int digit(int num, int exp) { - // k の代わりに exp を渡すことで、ここでコストの高い累乗計算の繰り返しを避けることができる + // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す return (num / exp) % 10; } - /* 計数ソート(nums の k 番目の桁に基づく) */ + /* 計数ソート(nums の k 桁目でソート) */ static void countingSortDigit(int[] nums, int exp) { - // 10進数の桁の範囲は 0~9、したがって長さ 10 のバケット配列が必要 + // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 int[] counter = new int[10]; int n = nums.length; - // 桁 0~9 の出現回数を統計 + // 0~9 の各数字の出現回数を集計する for (int i = 0; i < n; i++) { - int d = digit(nums[i], exp); // nums[i] の k 番目の桁を取得、d とする - counter[d]++; // 桁 d の出現回数を統計 + int d = digit(nums[i], exp); // nums[i] の第 k 位を取得し、d とする + counter[d]++; // 数字 d の出現回数を数える } - // 累積和を計算し、「出現回数」を「配列インデックス」に変換 + // 累積和を求め、「出現回数」を「配列インデックス」に変換する for (int i = 1; i < 10; i++) { counter[i] += counter[i - 1]; } - // 逆順に走査し、バケット統計に基づいて各要素を res に配置 + // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する int[] res = new int[n]; for (int i = n - 1; i >= 0; i--) { int d = digit(nums[i], exp); - int j = counter[d] - 1; // 配列内での d のインデックス j を取得 - res[j] = nums[i]; // 現在の要素をインデックス j に配置 - counter[d]--; // d のカウントを 1 減らす + int j = counter[d] - 1; // d の配列内インデックス j を取得する + res[j] = nums[i]; // 現在の要素をインデックス j に格納する + counter[d]--; // d の個数を 1 減らす } - // 結果で元の配列 nums を上書き + // 結果で元の配列 nums を上書きする for (int i = 0; i < n; i++) nums[i] = res[i]; } /* 基数ソート */ static void radixSort(int[] nums) { - // 配列の最大要素を取得し、最大桁数を判定するために使用 + // 最大桁数の判定用に配列の最大要素を取得 int m = Integer.MIN_VALUE; for (int num : nums) if (num > m) m = num; - // 最下位桁から最上位桁まで走査 + // 下位桁から上位桁の順に走査する for (int exp = 1; exp <= m; exp *= 10) { - // 配列要素の k 番目の桁に対して計数ソートを実行 + // 配列要素の k 桁目に対して計数ソートを行う // k = 1 -> exp = 1 // k = 2 -> exp = 10 - // すなわち exp = 10^(k-1) + // つまり exp = 10^(k-1) countingSortDigit(nums, exp); } } @@ -64,6 +64,6 @@ public class radix_sort { int[] nums = { 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, 63832996 }; radixSort(nums); - System.out.println("基数ソート後、nums = " + Arrays.toString(nums)); + System.out.println("基数ソート完了後の nums = " + Arrays.toString(nums)); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_sorting/selection_sort.java b/ja/codes/java/chapter_sorting/selection_sort.java index 76abb5bb9..238cf0c27 100644 --- a/ja/codes/java/chapter_sorting/selection_sort.java +++ b/ja/codes/java/chapter_sorting/selection_sort.java @@ -12,15 +12,15 @@ public class selection_sort { /* 選択ソート */ public static void selectionSort(int[] nums) { int n = nums.length; - // 外側ループ: 未ソート範囲は [i, n-1] + // 外側ループ:未整列区間は [i, n-1] for (int i = 0; i < n - 1; i++) { - // 内側ループ: 未ソート範囲内で最小要素を見つける + // 内側のループ:未ソート区間の最小要素を見つける int k = i; for (int j = i + 1; j < n; j++) { if (nums[j] < nums[k]) k = j; // 最小要素のインデックスを記録 } - // 最小要素と未ソート範囲の最初の要素を交換 + // その最小要素を未整列区間の先頭要素と交換する int temp = nums[i]; nums[i] = nums[k]; nums[k] = temp; @@ -30,6 +30,6 @@ public class selection_sort { public static void main(String[] args) { int[] nums = { 4, 1, 3, 1, 5, 2 }; selectionSort(nums); - System.out.println("選択ソート後、nums = " + Arrays.toString(nums)); + System.out.println("選択ソート完了後の nums = " + Arrays.toString(nums)); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_stack_and_queue/array_deque.java b/ja/codes/java/chapter_stack_and_queue/array_deque.java index 5ea40e4fa..9ca9c7776 100644 --- a/ja/codes/java/chapter_stack_and_queue/array_deque.java +++ b/ja/codes/java/chapter_stack_and_queue/array_deque.java @@ -8,10 +8,10 @@ package chapter_stack_and_queue; import java.util.*; -/* 循環配列に基づく両端キュークラス */ +/* 循環配列ベースの両端キュー */ class ArrayDeque { private int[] nums; // 両端キューの要素を格納する配列 - private int front; // 先頭ポインタ、先頭要素を指す + private int front; // 先頭ポインタ。先頭要素を指す private int queSize; // 両端キューの長さ /* コンストラクタ */ @@ -35,63 +35,65 @@ class ArrayDeque { return queSize == 0; } - /* 循環配列インデックスを計算 */ + /* 循環配列のインデックスを計算 */ private int index(int i) { - // モジュロ演算により循環配列を実装 - // i が配列の末尾を超える場合、先頭に戻る - // i が配列の先頭を超える場合、末尾に戻る + // 剰余演算により配列の先頭と末尾をつなげる + // i が配列の末尾を越えたら先頭に戻る + // i が配列の先頭を越えて前に出たら末尾に戻る return (i + capacity()) % capacity(); } - /* 先頭エンキュー */ + /* キュー先頭にエンキュー */ public void pushFirst(int num) { if (queSize == capacity()) { - System.out.println("両端キューが満杯です"); + System.out.println("双方向キューは満杯です"); return; } - // 先頭ポインタを左に移動し、境界を越える場合は配列の末尾に回る + // 先頭ポインタを左に 1 つ移動する + // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする front = index(front - 1); - // 先頭に num を追加 + // num をキュー先頭に追加 nums[front] = num; queSize++; } - /* 末尾エンキュー */ + /* キュー末尾にエンキュー */ public void pushLast(int num) { if (queSize == capacity()) { - System.out.println("両端キューが満杯です"); + System.out.println("双方向キューは満杯です"); return; } - // 末尾ポインタを計算し、末尾に要素を追加 + // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す int rear = index(front + queSize); + // num をキュー末尾に追加 nums[rear] = num; queSize++; } - /* 先頭デキュー */ + /* キュー先頭からデキュー */ public int popFirst() { int num = peekFirst(); - // 先頭ポインタを右に移動 + // 先頭ポインタを 1 つ後ろへ進める front = index(front + 1); queSize--; return num; } - /* 末尾デキュー */ + /* キュー末尾からデキュー */ public int popLast() { int num = peekLast(); queSize--; return num; } - /* 先頭要素にアクセス */ + /* キュー先頭の要素にアクセス */ public int peekFirst() { if (isEmpty()) throw new IndexOutOfBoundsException(); return nums[front]; } - /* 末尾要素にアクセス */ + /* キュー末尾の要素にアクセス */ public int peekLast() { if (isEmpty()) throw new IndexOutOfBoundsException(); @@ -100,9 +102,9 @@ class ArrayDeque { return nums[last]; } - /* 配列を返す */ + /* 出力用の配列を返す */ public int[] toArray() { - // front から開始して queSize 個の要素のみをコピー + // 有効長の範囲内のリスト要素のみを変換 int[] res = new int[queSize]; for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[index(j)]; @@ -114,38 +116,36 @@ class ArrayDeque { public class array_deque { public static void main(String[] args) { /* 両端キューを初期化 */ - int capacity = 10; - ArrayDeque deque = new ArrayDeque(capacity); - - /* 末尾エンキュー */ + ArrayDeque deque = new ArrayDeque(10); deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); - System.out.println("末尾エンキュー後 deque = " + Arrays.toString(deque.toArray())); + System.out.println("双方向キュー deque = " + Arrays.toString(deque.toArray())); - /* 先頭エンキュー */ - deque.pushFirst(4); - deque.pushFirst(1); - System.out.println("先頭エンキュー後 deque = " + Arrays.toString(deque.toArray())); - - /* 要素へのアクセス */ + /* 要素にアクセス */ int peekFirst = deque.peekFirst(); System.out.println("先頭要素 peekFirst = " + peekFirst); int peekLast = deque.peekLast(); System.out.println("末尾要素 peekLast = " + peekLast); - /* 要素デキュー */ - int popFirst = deque.popFirst(); - System.out.println("先頭デキュー要素 = " + popFirst + "、先頭デキュー後 deque = " + Arrays.toString(deque.toArray())); + /* 要素をエンキュー */ + deque.pushLast(4); + System.out.println("要素 4 を末尾にエンキューした後の deque = " + Arrays.toString(deque.toArray())); + deque.pushFirst(1); + System.out.println("要素 1 を先頭にエンキューした後の deque = " + Arrays.toString(deque.toArray())); + + /* 要素をデキュー */ int popLast = deque.popLast(); - System.out.println("末尾デキュー要素 = " + popLast + "、末尾デキュー後 deque = " + Arrays.toString(deque.toArray())); + System.out.println("末尾からデキューした要素 = " + popLast + "、末尾からデキューした後の deque = " + Arrays.toString(deque.toArray())); + int popFirst = deque.popFirst(); + System.out.println("先頭からデキューした要素 = " + popFirst + "、先頭からデキューした後の deque = " + Arrays.toString(deque.toArray())); /* 両端キューの長さを取得 */ int size = deque.size(); - System.out.println("両端キューの長さ size = " + size); + System.out.println("双方向キューの長さ size = " + size); /* 両端キューが空かどうかを判定 */ boolean isEmpty = deque.isEmpty(); - System.out.println("両端キューが空か = " + isEmpty); + System.out.println("双方向キューが空かどうか = " + isEmpty); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_stack_and_queue/array_queue.java b/ja/codes/java/chapter_stack_and_queue/array_queue.java index 420878b50..8d94e0764 100644 --- a/ja/codes/java/chapter_stack_and_queue/array_queue.java +++ b/ja/codes/java/chapter_stack_and_queue/array_queue.java @@ -8,10 +8,10 @@ package chapter_stack_and_queue; import java.util.*; -/* 配列に基づくキュークラス */ +/* 循環配列ベースのキュー */ class ArrayQueue { - private int[] nums; // 要素を格納する配列 - private int front; // キューヘッドポインタ、最初の要素を指す + private int[] nums; // キュー要素を格納する配列 + private int front; // 先頭ポインタ。先頭要素を指す private int queSize; // キューの長さ public ArrayQueue(int capacity) { @@ -37,13 +37,13 @@ class ArrayQueue { /* エンキュー */ public void push(int num) { if (queSize == capacity()) { - System.out.println("キューが満杯です"); + System.out.println("キューは満杯です"); return; } - // リアポインタを計算:front + queSize - // モジュロ操作により rear が配列の長さを超えることを回避 + // 末尾ポインタを計算し、末尾インデックス + 1 を指す + // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする int rear = (front + queSize) % capacity(); - // 要素をキューリアに追加 + // num をキュー末尾に追加 nums[rear] = num; queSize++; } @@ -51,13 +51,13 @@ class ArrayQueue { /* デキュー */ public int pop() { int num = peek(); - // キューヘッドポインタを後ろに1つ移動、モジュロ操作により範囲を超えることを回避 + // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す front = (front + 1) % capacity(); queSize--; return num; } - /* キューヘッド要素にアクセス */ + /* キュー先頭の要素にアクセス */ public int peek() { if (isEmpty()) throw new IndexOutOfBoundsException(); @@ -66,7 +66,7 @@ class ArrayQueue { /* 配列を返す */ public int[] toArray() { - // front から開始して queSize 個の要素のみをコピー + // 有効長の範囲内のリスト要素のみを変換 int[] res = new int[queSize]; for (int i = 0, j = front; i < queSize; i++, j++) { res[i] = nums[j % capacity()]; @@ -89,26 +89,27 @@ public class array_queue { queue.push(4); System.out.println("キュー queue = " + Arrays.toString(queue.toArray())); - /* キューヘッド要素にアクセス */ + /* キュー先頭の要素にアクセス */ int peek = queue.peek(); - System.out.println("キューヘッド要素 peek = " + peek); + System.out.println("先頭要素 peek = " + peek); /* 要素をデキュー */ int pop = queue.pop(); - System.out.println("デキューした要素 = " + pop + "、デキュー後 " + Arrays.toString(queue.toArray())); + System.out.println("デキューした要素 pop = " + pop + "、デキュー後の queue = " + Arrays.toString(queue.toArray())); /* キューの長さを取得 */ int size = queue.size(); System.out.println("キューの長さ size = " + size); - /* 空かどうかを判定 */ + /* キューが空かどうかを判定 */ boolean isEmpty = queue.isEmpty(); - System.out.println("キューが空か = " + isEmpty); + System.out.println("キューが空かどうか = " + isEmpty); - /* 連続エンキューのテスト */ + /* 循環配列をテストする */ for (int i = 0; i < 10; i++) { queue.push(i); + queue.pop(); + System.out.println("第 " + i + " ラウンドのエンキュー + デキュー後の queue = " + Arrays.toString(queue.toArray())); } - System.out.println("連続エンキュー後 queue = " + Arrays.toString(queue.toArray())); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_stack_and_queue/array_stack.java b/ja/codes/java/chapter_stack_and_queue/array_stack.java index 07f573cec..cde91f0be 100644 --- a/ja/codes/java/chapter_stack_and_queue/array_stack.java +++ b/ja/codes/java/chapter_stack_and_queue/array_stack.java @@ -8,12 +8,12 @@ package chapter_stack_and_queue; import java.util.*; -/* 配列に基づくスタッククラス */ +/* 配列ベースのスタック */ class ArrayStack { private ArrayList stack; public ArrayStack() { - // リスト(動的配列)を初期化 + // リスト(動的配列)を初期化する stack = new ArrayList<>(); } @@ -39,7 +39,7 @@ class ArrayStack { return stack.remove(size() - 1); } - /* スタックトップ要素にアクセス */ + /* スタックトップの要素にアクセス */ public int peek() { if (isEmpty()) throw new IndexOutOfBoundsException(); @@ -65,13 +65,13 @@ public class array_stack { stack.push(4); System.out.println("スタック stack = " + Arrays.toString(stack.toArray())); - /* スタックトップ要素にアクセス */ + /* スタックトップの要素にアクセス */ int peek = stack.peek(); System.out.println("スタックトップ要素 peek = " + peek); /* 要素をポップ */ int pop = stack.pop(); - System.out.println("ポップした要素 = " + pop + "、ポップ後 " + Arrays.toString(stack.toArray())); + System.out.println("ポップした要素 pop = " + pop + "、ポップ後の stack = " + Arrays.toString(stack.toArray())); /* スタックの長さを取得 */ int size = stack.size(); @@ -79,6 +79,6 @@ public class array_stack { /* 空かどうかを判定 */ boolean isEmpty = stack.isEmpty(); - System.out.println("スタックが空か = " + isEmpty); + System.out.println("スタックが空かどうか = " + isEmpty); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_stack_and_queue/deque.java b/ja/codes/java/chapter_stack_and_queue/deque.java index 0c7b5cfc9..a30db1a63 100644 --- a/ja/codes/java/chapter_stack_and_queue/deque.java +++ b/ja/codes/java/chapter_stack_and_queue/deque.java @@ -15,32 +15,32 @@ public class deque { deque.offerLast(3); deque.offerLast(2); deque.offerLast(5); - System.out.println("両端キュー deque = " + deque); + System.out.println("双方向キュー deque = " + deque); - /* 要素へのアクセス */ + /* 要素にアクセス */ int peekFirst = deque.peekFirst(); System.out.println("先頭要素 peekFirst = " + peekFirst); int peekLast = deque.peekLast(); System.out.println("末尾要素 peekLast = " + peekLast); - /* 要素のエンキュー */ + /* 要素をエンキュー */ deque.offerLast(4); - System.out.println("要素4を末尾にエンキュー、deque = " + deque); + System.out.println("要素 4 を末尾にエンキューした後の deque = " + deque); deque.offerFirst(1); - System.out.println("要素1を先頭にエンキュー、deque = " + deque); + System.out.println("要素 1 を先頭にエンキューした後の deque = " + deque); - /* 要素のデキュー */ + /* 要素をデキュー */ int popLast = deque.pollLast(); - System.out.println("両端キュー末尾要素 = " + popLast + "、末尾からデキュー後 " + deque); + System.out.println("末尾からデキューした要素 = " + popLast + "、末尾からデキューした後の deque = " + deque); int popFirst = deque.pollFirst(); - System.out.println("両端キュー先頭要素 = " + popFirst + "、先頭からデキュー後 " + deque); + System.out.println("先頭からデキューした要素 = " + popFirst + "、先頭からデキューした後の deque = " + deque); /* 両端キューの長さを取得 */ int size = deque.size(); - System.out.println("両端キューの長さ size = " + size); + System.out.println("双方向キューの長さ size = " + size); /* 両端キューが空かどうかを判定 */ boolean isEmpty = deque.isEmpty(); - System.out.println("両端キューが空か = " + isEmpty); + System.out.println("双方向キューが空かどうか = " + isEmpty); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_stack_and_queue/linkedlist_deque.java b/ja/codes/java/chapter_stack_and_queue/linkedlist_deque.java index f8861bbe7..6f2f26dfe 100644 --- a/ja/codes/java/chapter_stack_and_queue/linkedlist_deque.java +++ b/ja/codes/java/chapter_stack_and_queue/linkedlist_deque.java @@ -12,7 +12,7 @@ import java.util.*; class ListNode { int val; // ノード値 ListNode next; // 後続ノードへの参照 - ListNode prev; // 前任ノードへの参照 + ListNode prev; // 前駆ノードへの参照 ListNode(int val) { this.val = val; @@ -20,7 +20,7 @@ class ListNode { } } -/* 双方向連結リストに基づく両端キュークラス */ +/* 双方向連結リストベースの両端キュー */ class LinkedListDeque { private ListNode front, rear; // 先頭ノード front、末尾ノード rear private int queSize = 0; // 両端キューの長さ @@ -42,31 +42,31 @@ class LinkedListDeque { /* エンキュー操作 */ private void push(int num, boolean isFront) { ListNode node = new ListNode(num); - // リストが空の場合、front と rear の両方を node に指す + // 連結リストが空なら、front と rear の両方を node に向ける if (isEmpty()) front = rear = node; - // 先頭エンキュー操作 + // 先頭へのエンキュー操作 else if (isFront) { - // node をリストの先頭に追加 + // node を連結リストの先頭に追加 front.prev = node; node.next = front; - front = node; // front を更新 - // 末尾エンキュー操作 + front = node; // 先頭ノードを更新する + // 末尾へのエンキュー操作 } else { - // node をリストの末尾に追加 + // node を連結リストの末尾に追加 rear.next = node; node.prev = rear; - rear = node; // rear を更新 + rear = node; // 末尾ノードを更新する } - queSize++; // 長さを更新 + queSize++; // キューの長さを更新 } - /* 先頭エンキュー */ + /* キュー先頭にエンキュー */ public void pushFirst(int num) { push(num, true); } - /* 末尾エンキュー */ + /* キュー末尾にエンキュー */ public void pushLast(int num) { push(num, false); } @@ -76,56 +76,56 @@ class LinkedListDeque { if (isEmpty()) throw new IndexOutOfBoundsException(); int val; - // 先頭デキュー操作 + // キュー先頭からの取り出し if (isFront) { - val = front.val; // 一時的に先頭ノード値を保存 - // 次のノードを削除 + val = front.val; // 先頭ノードの値を一時保存 + // 先頭ノードを削除 ListNode fNext = front.next; if (fNext != null) { fNext.prev = null; front.next = null; } - front = fNext; // front を更新 - // 末尾デキュー操作 + front = fNext; // 先頭ノードを更新する + // キュー末尾からの取り出し } else { - val = rear.val; // 一時的に末尾ノード値を保存 - // 前のノードを削除 + val = rear.val; // 末尾ノードの値を一時保存 + // 末尾ノードを削除 ListNode rPrev = rear.prev; if (rPrev != null) { rPrev.next = null; rear.prev = null; } - rear = rPrev; // rear を更新 + rear = rPrev; // 末尾ノードを更新する } - queSize--; // 長さを更新 + queSize--; // キューの長さを更新 return val; } - /* 先頭デキュー */ + /* キュー先頭からデキュー */ public int popFirst() { return pop(true); } - /* 末尾デキュー */ + /* キュー末尾からデキュー */ public int popLast() { return pop(false); } - /* 先頭要素にアクセス */ + /* キュー先頭の要素にアクセス */ public int peekFirst() { if (isEmpty()) throw new IndexOutOfBoundsException(); return front.val; } - /* 末尾要素にアクセス */ + /* キュー末尾の要素にアクセス */ public int peekLast() { if (isEmpty()) throw new IndexOutOfBoundsException(); return rear.val; } - /* 配列を返す */ + /* 出力用の配列を返す */ public int[] toArray() { ListNode node = front; int[] res = new int[size()]; @@ -141,36 +141,35 @@ public class linkedlist_deque { public static void main(String[] args) { /* 両端キューを初期化 */ LinkedListDeque deque = new LinkedListDeque(); - - /* 末尾エンキュー */ deque.pushLast(3); deque.pushLast(2); deque.pushLast(5); - System.out.println("末尾エンキュー後 deque = " + Arrays.toString(deque.toArray())); + System.out.println("双方向キュー deque = " + Arrays.toString(deque.toArray())); - /* 先頭エンキュー */ - deque.pushFirst(4); - deque.pushFirst(1); - System.out.println("先頭エンキュー後 deque = " + Arrays.toString(deque.toArray())); - - /* 要素へのアクセス */ + /* 要素にアクセス */ int peekFirst = deque.peekFirst(); System.out.println("先頭要素 peekFirst = " + peekFirst); int peekLast = deque.peekLast(); System.out.println("末尾要素 peekLast = " + peekLast); - /* 要素デキュー */ - int popFirst = deque.popFirst(); - System.out.println("先頭デキュー要素 = " + popFirst + "、先頭デキュー後 deque = " + Arrays.toString(deque.toArray())); + /* 要素をエンキュー */ + deque.pushLast(4); + System.out.println("要素 4 を末尾にエンキューした後の deque = " + Arrays.toString(deque.toArray())); + deque.pushFirst(1); + System.out.println("要素 1 を先頭にエンキューした後の deque = " + Arrays.toString(deque.toArray())); + + /* 要素をデキュー */ int popLast = deque.popLast(); - System.out.println("末尾デキュー要素 = " + popLast + "、末尾デキュー後 deque = " + Arrays.toString(deque.toArray())); + System.out.println("末尾からデキューした要素 = " + popLast + "、末尾からデキューした後の deque = " + Arrays.toString(deque.toArray())); + int popFirst = deque.popFirst(); + System.out.println("先頭からデキューした要素 = " + popFirst + "、先頭からデキューした後の deque = " + Arrays.toString(deque.toArray())); /* 両端キューの長さを取得 */ int size = deque.size(); - System.out.println("両端キューの長さ size = " + size); + System.out.println("双方向キューの長さ size = " + size); /* 両端キューが空かどうかを判定 */ boolean isEmpty = deque.isEmpty(); - System.out.println("両端キューが空か = " + isEmpty); + System.out.println("双方向キューが空かどうか = " + isEmpty); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_stack_and_queue/linkedlist_queue.java b/ja/codes/java/chapter_stack_and_queue/linkedlist_queue.java index fb3347fe0..d0d1cff40 100644 --- a/ja/codes/java/chapter_stack_and_queue/linkedlist_queue.java +++ b/ja/codes/java/chapter_stack_and_queue/linkedlist_queue.java @@ -8,7 +8,7 @@ package chapter_stack_and_queue; import java.util.*; -/* 連結リストに基づくキュークラス */ +/* 連結リストベースのキュー */ class LinkedListQueue { private ListNode front, rear; // 先頭ノード front、末尾ノード rear private int queSize = 0; @@ -32,11 +32,11 @@ class LinkedListQueue { public void push(int num) { // 末尾ノードの後ろに num を追加 ListNode node = new ListNode(num); - // キューが空の場合、先頭と末尾ノードの両方をそのノードにポイント + // キューが空なら、先頭・末尾ノードをともにそのノードに設定 if (front == null) { front = node; rear = node; - // キューが空でない場合、そのノードを末尾ノードの後ろに追加 + // キューが空でなければ、そのノードを末尾ノードの後ろに追加 } else { rear.next = node; rear = node; @@ -53,14 +53,14 @@ class LinkedListQueue { return num; } - /* 先頭要素にアクセス */ + /* キュー先頭の要素にアクセス */ public int peek() { if (isEmpty()) throw new IndexOutOfBoundsException(); return front.val; } - /* 連結リストを配列に変換して返す */ + /* 連結リストを Array に変換して返す */ public int[] toArray() { ListNode node = front; int[] res = new int[size()]; @@ -85,13 +85,13 @@ public class linkedlist_queue { queue.push(4); System.out.println("キュー queue = " + Arrays.toString(queue.toArray())); - /* 先頭要素にアクセス */ + /* キュー先頭の要素にアクセス */ int peek = queue.peek(); System.out.println("先頭要素 peek = " + peek); /* 要素をデキュー */ int pop = queue.pop(); - System.out.println("デキューした要素 = " + pop + "、デキュー後 " + Arrays.toString(queue.toArray())); + System.out.println("デキューした要素 pop = " + pop + "、デキュー後の queue = " + Arrays.toString(queue.toArray())); /* キューの長さを取得 */ int size = queue.size(); @@ -99,6 +99,6 @@ public class linkedlist_queue { /* キューが空かどうかを判定 */ boolean isEmpty = queue.isEmpty(); - System.out.println("キューが空か = " + isEmpty); + System.out.println("キューが空かどうか = " + isEmpty); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_stack_and_queue/linkedlist_stack.java b/ja/codes/java/chapter_stack_and_queue/linkedlist_stack.java index 2587ee676..97b74a987 100644 --- a/ja/codes/java/chapter_stack_and_queue/linkedlist_stack.java +++ b/ja/codes/java/chapter_stack_and_queue/linkedlist_stack.java @@ -9,9 +9,9 @@ package chapter_stack_and_queue; import java.util.*; import utils.*; -/* 連結リストに基づくスタッククラス */ +/* 連結リストベースのスタック */ class LinkedListStack { - private ListNode stackPeek; // ヘッドノードをスタックトップとして使用 + private ListNode stackPeek; // 先頭ノードをスタックトップとする private int stkSize = 0; // スタックの長さ public LinkedListStack() { @@ -44,7 +44,7 @@ class LinkedListStack { return num; } - /* スタックトップ要素にアクセス */ + /* スタックトップの要素にアクセス */ public int peek() { if (isEmpty()) throw new IndexOutOfBoundsException(); @@ -76,13 +76,13 @@ public class linkedlist_stack { stack.push(4); System.out.println("スタック stack = " + Arrays.toString(stack.toArray())); - /* スタックトップ要素にアクセス */ + /* スタックトップの要素にアクセス */ int peek = stack.peek(); System.out.println("スタックトップ要素 peek = " + peek); /* 要素をポップ */ int pop = stack.pop(); - System.out.println("ポップした要素 = " + pop + "、ポップ後 " + Arrays.toString(stack.toArray())); + System.out.println("ポップした要素 pop = " + pop + "、ポップ後の stack = " + Arrays.toString(stack.toArray())); /* スタックの長さを取得 */ int size = stack.size(); @@ -90,6 +90,6 @@ public class linkedlist_stack { /* 空かどうかを判定 */ boolean isEmpty = stack.isEmpty(); - System.out.println("スタックが空か = " + isEmpty); + System.out.println("スタックが空かどうか = " + isEmpty); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_stack_and_queue/queue.java b/ja/codes/java/chapter_stack_and_queue/queue.java index 48383195c..aaa96f3eb 100644 --- a/ja/codes/java/chapter_stack_and_queue/queue.java +++ b/ja/codes/java/chapter_stack_and_queue/queue.java @@ -21,13 +21,13 @@ public class queue { queue.offer(4); System.out.println("キュー queue = " + queue); - /* 先頭要素にアクセス */ + /* キュー先頭の要素にアクセス */ int peek = queue.peek(); System.out.println("先頭要素 peek = " + peek); /* 要素をデキュー */ int pop = queue.poll(); - System.out.println("デキューした要素 = " + pop + "、デキュー後 " + queue); + System.out.println("デキューした要素 pop = " + pop + "、デキュー後の queue = " + queue); /* キューの長さを取得 */ int size = queue.size(); @@ -35,6 +35,6 @@ public class queue { /* キューが空かどうかを判定 */ boolean isEmpty = queue.isEmpty(); - System.out.println("キューが空か = " + isEmpty); + System.out.println("キューが空かどうか = " + isEmpty); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_stack_and_queue/stack.java b/ja/codes/java/chapter_stack_and_queue/stack.java index 7793a0d5c..9b3093b2d 100644 --- a/ja/codes/java/chapter_stack_and_queue/stack.java +++ b/ja/codes/java/chapter_stack_and_queue/stack.java @@ -21,13 +21,13 @@ public class stack { stack.push(4); System.out.println("スタック stack = " + stack); - /* スタックトップ要素にアクセス */ + /* スタックトップの要素にアクセス */ int peek = stack.peek(); System.out.println("スタックトップ要素 peek = " + peek); /* 要素をポップ */ int pop = stack.pop(); - System.out.println("ポップした要素 = " + pop + "、ポップ後 " + stack); + System.out.println("ポップした要素 pop = " + pop + "、ポップ後の stack = " + stack); /* スタックの長さを取得 */ int size = stack.size(); @@ -35,6 +35,6 @@ public class stack { /* 空かどうかを判定 */ boolean isEmpty = stack.isEmpty(); - System.out.println("スタックが空か = " + isEmpty); + System.out.println("スタックが空かどうか = " + isEmpty); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_tree/array_binary_tree.java b/ja/codes/java/chapter_tree/array_binary_tree.java index 80a075dc1..c20e40304 100644 --- a/ja/codes/java/chapter_tree/array_binary_tree.java +++ b/ja/codes/java/chapter_tree/array_binary_tree.java @@ -9,7 +9,7 @@ package chapter_tree; import utils.*; import java.util.*; -/* 配列ベースの二分木クラス */ +/* 配列表現による二分木クラス */ class ArrayBinaryTree { private List tree; @@ -18,30 +18,30 @@ class ArrayBinaryTree { tree = new ArrayList<>(arr); } - /* リストの容量 */ + /* リスト容量 */ public int size() { return tree.size(); } /* インデックス i のノードの値を取得 */ public Integer val(int i) { - // インデックスが範囲外の場合、null を返す(空の位置を表す) + // インデックスが範囲外なら、空きを表す null を返す if (i < 0 || i >= size()) return null; return tree.get(i); } - /* インデックス i のノードの左の子のインデックスを取得 */ + /* インデックス i のノードの左子ノードのインデックスを取得 */ public Integer left(int i) { return 2 * i + 1; } - /* インデックス i のノードの右の子のインデックスを取得 */ + /* インデックス i のノードの右子ノードのインデックスを取得 */ public Integer right(int i) { return 2 * i + 2; } - /* インデックス i のノードの親のインデックスを取得 */ + /* インデックス i のノードの親ノードのインデックスを取得 */ public Integer parent(int i) { return (i - 1) / 2; } @@ -49,7 +49,7 @@ class ArrayBinaryTree { /* レベル順走査 */ public List levelOrder() { List res = new ArrayList<>(); - // 配列を走査 + // 配列を直接走査する for (int i = 0; i < size(); i++) { if (val(i) != null) res.add(val(i)); @@ -57,12 +57,12 @@ class ArrayBinaryTree { return res; } - /* 深さ優先走査 */ + /* 深さ優先探索 */ private void dfs(Integer i, String order, List res) { - // 空の位置の場合、戻る + // 空きスロットなら返す if (val(i) == null) return; - // 前順走査 + // 先行順走査 if ("pre".equals(order)) res.add(val(i)); dfs(left(i), order, res); @@ -75,7 +75,7 @@ class ArrayBinaryTree { res.add(val(i)); } - /* 前順走査 */ + /* 先行順走査 */ public List preOrder() { List res = new ArrayList<>(); dfs(0, "pre", res); @@ -100,7 +100,7 @@ class ArrayBinaryTree { public class array_binary_tree { public static void main(String[] args) { // 二分木を初期化 - // 特定の関数を使用して配列を二分木に変換 + // ここでは、配列から直接二分木を生成する関数を利用する List arr = Arrays.asList(1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15); TreeNode root = TreeNode.listToTree(arr); @@ -110,7 +110,7 @@ public class array_binary_tree { System.out.println("二分木の連結リスト表現:"); PrintUtil.printTree(root); - // 配列ベースの二分木クラス + // 配列表現による二分木クラス ArrayBinaryTree abt = new ArrayBinaryTree(arr); // ノードにアクセス @@ -118,19 +118,19 @@ public class array_binary_tree { Integer l = abt.left(i); Integer r = abt.right(i); Integer p = abt.parent(i); - System.out.println("\n現在のノードのインデックスは " + i + "、値 = " + abt.val(i)); - System.out.println("その左の子のインデックスは " + l + "、値 = " + (l == null ? "null" : abt.val(l))); - System.out.println("その右の子のインデックスは " + r + "、値 = " + (r == null ? "null" : abt.val(r))); - System.out.println("その親のインデックスは " + p + "、値 = " + (p == null ? "null" : abt.val(p))); + System.out.println("\n現在のノードのインデックスは " + i + "、値は " + abt.val(i)); + System.out.println("その左子ノードのインデックスは " + l + "、値は " + (l == null ? "null" : abt.val(l))); + System.out.println("その右子ノードのインデックスは " + r + "、値は " + (r == null ? "null" : abt.val(r))); + System.out.println("その親ノードのインデックスは " + p + "、値は " + (p == null ? "null" : abt.val(p))); // 木を走査 List res = abt.levelOrder(); - System.out.println("\nレベル順走査は:" + res); + System.out.println("\nレベル順走査: " + res); res = abt.preOrder(); - System.out.println("前順走査は:" + res); + System.out.println("前順走査: " + res); res = abt.inOrder(); - System.out.println("中順走査は:" + res); + System.out.println("中順走査: " + res); res = abt.postOrder(); - System.out.println("後順走査は:" + res); + System.out.println("後順走査: " + res); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_tree/avl_tree.java b/ja/codes/java/chapter_tree/avl_tree.java index a7261b9af..155546462 100644 --- a/ja/codes/java/chapter_tree/avl_tree.java +++ b/ja/codes/java/chapter_tree/avl_tree.java @@ -8,7 +8,7 @@ package chapter_tree; import utils.*; -/* AVL木 */ +/* AVL 木 */ class AVLTree { TreeNode root; // 根ノード @@ -18,76 +18,76 @@ class AVLTree { return node == null ? -1 : node.height; } - /* ノードの高さを更新 */ + /* ノードの高さを更新する */ private void updateHeight(TreeNode node) { - // ノードの高さは最も高い部分木の高さ + 1 + // ノードの高さは最も高い部分木の高さ + 1 に等しい node.height = Math.max(height(node.left), height(node.right)) + 1; } - /* 平衡因子を取得 */ + /* 平衡係数を取得 */ public int balanceFactor(TreeNode node) { - // 空ノードの平衡因子は 0 + // 空ノードの平衡係数は 0 if (node == null) return 0; - // ノードの平衡因子 = 左部分木の高さ - 右部分木の高さ + // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ return height(node.left) - height(node.right); } - /* 右回転操作 */ + /* 右回転 */ private TreeNode rightRotate(TreeNode node) { TreeNode child = node.left; TreeNode grandChild = child.right; - // child を軸として node を右に回転 + // child を支点として node を右回転させる child.right = node; node.left = grandChild; - // ノードの高さを更新 + // ノードの高さを更新する updateHeight(node); updateHeight(child); - // 回転後の部分木の根を返す + // 回転後の部分木の根ノードを返す return child; } - /* 左回転操作 */ + /* 左回転 */ private TreeNode leftRotate(TreeNode node) { TreeNode child = node.right; TreeNode grandChild = child.left; - // child を軸として node を左に回転 + // child を支点として node を左回転させる child.left = node; node.right = grandChild; - // ノードの高さを更新 + // ノードの高さを更新する updateHeight(node); updateHeight(child); - // 回転後の部分木の根を返す + // 回転後の部分木の根ノードを返す return child; } - /* 回転操作を実行して部分木の平衡を回復 */ + /* 回転操作を行い、この部分木の平衡を回復する */ private TreeNode rotate(TreeNode node) { - // node の平衡因子を取得 + // ノード node の平衡係数を取得 int balanceFactor = balanceFactor(node); - // 左傾斜の木 + // 左に偏った木 if (balanceFactor > 1) { if (balanceFactor(node.left) >= 0) { // 右回転 return rightRotate(node); } else { - // 先に左回転、その後右回転 + // 左回転してから右回転 node.left = leftRotate(node.left); return rightRotate(node); } } - // 右傾斜の木 + // 右に偏った木 if (balanceFactor < -1) { if (balanceFactor(node.right) <= 0) { // 左回転 return leftRotate(node); } else { - // 先に右回転、その後左回転 + // 右回転してから左回転 node.right = rightRotate(node.right); return leftRotate(node); } } - // 平衡木、回転は不要、戻る + // 平衡木なので回転不要、そのまま返す return node; } @@ -96,19 +96,19 @@ class AVLTree { root = insertHelper(root, val); } - /* 再帰的にノードを挿入(補助メソッド) */ + /* ノードを再帰的に挿入する(補助メソッド) */ private TreeNode insertHelper(TreeNode node, int val) { if (node == null) return new TreeNode(val); - /* 1. 挿入位置を見つけてノードを挿入 */ + /* 1. 挿入位置を探索してノードを挿入 */ if (val < node.val) node.left = insertHelper(node.left, val); else if (val > node.val) node.right = insertHelper(node.right, val); else - return node; // 重複ノードは挿入しない、戻る - updateHeight(node); // ノードの高さを更新 - /* 2. 回転操作を実行して部分木の平衡を回復 */ + return node; // 重複ノードは挿入せず、そのまま返す + updateHeight(node); // ノードの高さを更新する + /* 2. 回転操作を行い、部分木の平衡を回復する */ node = rotate(node); // 部分木の根ノードを返す return node; @@ -119,11 +119,11 @@ class AVLTree { root = removeHelper(root, val); } - /* 再帰的にノードを削除(補助メソッド) */ + /* ノードを再帰的に削除する(補助メソッド) */ private TreeNode removeHelper(TreeNode node, int val) { if (node == null) return null; - /* 1. ノードを見つけて削除 */ + /* 1. ノードを探索して削除 */ if (val < node.val) node.left = removeHelper(node.left, val); else if (val > node.val) @@ -131,14 +131,14 @@ class AVLTree { else { if (node.left == null || node.right == null) { TreeNode child = node.left != null ? node.left : node.right; - // 子ノード数 = 0、ノードを削除して戻る + // 子ノード数 = 0 の場合、node をそのまま削除して返す if (child == null) return null; - // 子ノード数 = 1、ノードを削除 + // 子ノード数 = 1 の場合、node をそのまま削除する else node = child; } else { - // 子ノード数 = 2、中順走査の次のノードを削除し、現在のノードをそれで置き換える + // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える TreeNode temp = node.right; while (temp.left != null) { temp = temp.left; @@ -147,29 +147,29 @@ class AVLTree { node.val = temp.val; } } - updateHeight(node); // ノードの高さを更新 - /* 2. 回転操作を実行して部分木の平衡を回復 */ + updateHeight(node); // ノードの高さを更新する + /* 2. 回転操作を行い、部分木の平衡を回復する */ node = rotate(node); // 部分木の根ノードを返す return node; } - /* ノードを検索 */ + /* ノードを探索 */ public TreeNode search(int val) { TreeNode cur = root; - // ループで検索、葉ノードを通過後に終了 + // ループで探索し、葉ノードを越えたら抜ける while (cur != null) { - // 対象ノードは cur の右部分木にある + // 目標ノードは cur の右部分木にある if (cur.val < val) cur = cur.right; - // 対象ノードは cur の左部分木にある + // 目標ノードは cur の左部分木にある else if (cur.val > val) cur = cur.left; - // 対象ノードを見つけた、ループを終了 + // 目標ノードが見つかったらループを抜ける else break; } - // 対象ノードを返す + // 目標ノードを返す return cur; } } @@ -177,22 +177,22 @@ class AVLTree { public class avl_tree { static void testInsert(AVLTree tree, int val) { tree.insert(val); - System.out.println("\nノード " + val + " を挿入後、AVL木は "); + System.out.println("\nノード " + val + " を挿入した後、AVL 木は"); PrintUtil.printTree(tree.root); } static void testRemove(AVLTree tree, int val) { tree.remove(val); - System.out.println("\nノード " + val + " を削除後、AVL木は "); + System.out.println("\nノード " + val + " を削除した後、AVL 木は"); PrintUtil.printTree(tree.root); } public static void main(String[] args) { - /* 空のAVL木を初期化 */ + /* 空の AVL 木を初期化する */ AVLTree avlTree = new AVLTree(); /* ノードを挿入 */ - // ノード挿入後にAVL木がどのように平衡を保つかを確認 + // ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい testInsert(avlTree, 1); testInsert(avlTree, 2); testInsert(avlTree, 3); @@ -204,17 +204,17 @@ public class avl_tree { testInsert(avlTree, 10); testInsert(avlTree, 6); - /* 重複ノードを挿入 */ + /* 重複ノードを挿入する */ testInsert(avlTree, 7); /* ノードを削除 */ - // ノード削除後にAVL木がどのように平衡を保つかを確認 - testRemove(avlTree, 8); // 次数 0 のノードを削除 - testRemove(avlTree, 5); // 次数 1 のノードを削除 - testRemove(avlTree, 4); // 次数 2 のノードを削除 + // ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい + testRemove(avlTree, 8); // 次数 0 のノードを削除する + testRemove(avlTree, 5); // 次数 1 のノードを削除する + testRemove(avlTree, 4); // 次数 2 のノードを削除する /* ノードを検索 */ TreeNode node = avlTree.search(7); - System.out.println("\n見つかったノードオブジェクトは " + node + "、ノードの値 = " + node.val); + System.out.println("\n見つかったノードオブジェクトは " + node + "、ノード値 = " + node.val); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_tree/binary_search_tree.java b/ja/codes/java/chapter_tree/binary_search_tree.java index f27245b4e..030da00f8 100644 --- a/ja/codes/java/chapter_tree/binary_search_tree.java +++ b/ja/codes/java/chapter_tree/binary_search_tree.java @@ -14,7 +14,7 @@ class BinarySearchTree { /* コンストラクタ */ public BinarySearchTree() { - // 空の木を初期化 + // 空の木を初期化する root = null; } @@ -23,36 +23,36 @@ class BinarySearchTree { return root; } - /* ノードを検索 */ + /* ノードを探索 */ public TreeNode search(int num) { TreeNode cur = root; - // ループで検索、葉ノードを通過後に終了 + // ループで探索し、葉ノードを越えたら抜ける while (cur != null) { - // 対象ノードは cur の右部分木にある + // 目標ノードは cur の右部分木にある if (cur.val < num) cur = cur.right; - // 対象ノードは cur の左部分木にある + // 目標ノードは cur の左部分木にある else if (cur.val > num) cur = cur.left; - // 対象ノードを見つけた、ループを終了 + // 目標ノードが見つかったらループを抜ける else break; } - // 対象ノードを返す + // 目標ノードを返す return cur; } /* ノードを挿入 */ public void insert(int num) { - // 木が空の場合、根ノードを初期化 + // 木が空なら、根ノードを初期化する if (root == null) { root = new TreeNode(num); return; } TreeNode cur = root, pre = null; - // ループで検索、葉ノードを通過後に終了 + // ループで探索し、葉ノードを越えたら抜ける while (cur != null) { - // 重複ノードを見つけた場合、戻る + // 重複ノードが見つかったら、直ちに返す if (cur.val == num) return; pre = cur; @@ -73,51 +73,51 @@ class BinarySearchTree { /* ノードを削除 */ public void remove(int num) { - // 木が空の場合、戻る + // 木が空なら、そのまま早期リターンする if (root == null) return; TreeNode cur = root, pre = null; - // ループで検索、葉ノードを通過後に終了 + // ループで探索し、葉ノードを越えたら抜ける while (cur != null) { - // 削除するノードを見つけた、ループを終了 + // 削除対象のノードが見つかったら、ループを抜ける if (cur.val == num) break; pre = cur; - // 削除するノードは cur の右部分木にある + // 削除対象ノードは cur の右部分木にある if (cur.val < num) cur = cur.right; - // 削除するノードは cur の左部分木にある + // 削除対象ノードは cur の左部分木にある else cur = cur.left; } - // 削除するノードがない場合、戻る + // 削除対象ノードがなければそのまま返す if (cur == null) return; - // 子ノード数 = 0 または 1 + // 子ノード数 = 0 or 1 if (cur.left == null || cur.right == null) { - // 子ノード数 = 0/1 の場合、child = null/その子ノード + // 子ノード数が 0 / 1 のとき、child = null / その子ノード TreeNode child = cur.left != null ? cur.left : cur.right; - // ノード cur を削除 + // ノード cur を削除する if (cur != root) { if (pre.left == cur) pre.left = child; else pre.right = child; } else { - // 削除されるノードが根の場合、根を再割り当て + // 削除ノードが根ノードなら、根ノードを再設定 root = child; } } // 子ノード数 = 2 else { - // cur の中順走査の次のノードを取得 + // 中順走査における cur の次ノードを取得 TreeNode tmp = cur.right; while (tmp.left != null) { tmp = tmp.left; } - // 再帰的にノード tmp を削除 + // ノード tmp を再帰的に削除 remove(tmp.val); - // cur を tmp で置き換える + // tmp で cur を上書きする cur.val = tmp.val; } } @@ -127,32 +127,32 @@ public class binary_search_tree { public static void main(String[] args) { /* 二分探索木を初期化 */ BinarySearchTree bst = new BinarySearchTree(); - // 異なる挿入順序は様々な木構造を生成できることに注意。この特定の順序は完全二分木を作成する + // 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる int[] nums = { 8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15 }; for (int num : nums) { bst.insert(num); } - System.out.println("\n初期化された二分木は\n"); + System.out.println("\n初期化した二分木は\n"); PrintUtil.printTree(bst.getRoot()); - /* ノードを検索 */ + /* ノードを探索 */ TreeNode node = bst.search(7); - System.out.println("\n見つかったノードオブジェクトは " + node + "、ノードの値 = " + node.val); + System.out.println("\n見つかったノードオブジェクトは " + node + "、ノード値 = " + node.val); /* ノードを挿入 */ bst.insert(16); - System.out.println("\nノード 16 を挿入後、二分木は\n"); + System.out.println("\nノード 16 を挿入した後の二分木は\n"); PrintUtil.printTree(bst.getRoot()); /* ノードを削除 */ bst.remove(1); - System.out.println("\nノード 1 を削除後、二分木は\n"); + System.out.println("\nノード 1 を削除後,二分木は\n"); PrintUtil.printTree(bst.getRoot()); bst.remove(2); - System.out.println("\nノード 2 を削除後、二分木は\n"); + System.out.println("\nノード 2 を削除後,二分木は\n"); PrintUtil.printTree(bst.getRoot()); bst.remove(4); - System.out.println("\nノード 4 を削除後、二分木は\n"); + System.out.println("\nノード 4 を削除後,二分木は\n"); PrintUtil.printTree(bst.getRoot()); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_tree/binary_tree.java b/ja/codes/java/chapter_tree/binary_tree.java index 78e84ea27..0025eb40b 100644 --- a/ja/codes/java/chapter_tree/binary_tree.java +++ b/ja/codes/java/chapter_tree/binary_tree.java @@ -17,7 +17,7 @@ public class binary_tree { TreeNode n3 = new TreeNode(3); TreeNode n4 = new TreeNode(4); TreeNode n5 = new TreeNode(5); - // ノードの参照(ポインタ)を構築 + // ノード間の参照(ポインタ)を構築する n1.left = n2; n1.right = n3; n2.left = n4; @@ -27,7 +27,7 @@ public class binary_tree { /* ノードの挿入と削除 */ TreeNode P = new TreeNode(0); - // ノード P を n1 -> n2 の間に挿入 + // n1 -> n2 の間にノード P を挿入 n1.left = P; P.left = n2; System.out.println("\nノード P を挿入後\n"); @@ -37,4 +37,4 @@ public class binary_tree { System.out.println("\nノード P を削除後\n"); PrintUtil.printTree(n1); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_tree/binary_tree_bfs.java b/ja/codes/java/chapter_tree/binary_tree_bfs.java index 50585b815..ea2d2a190 100644 --- a/ja/codes/java/chapter_tree/binary_tree_bfs.java +++ b/ja/codes/java/chapter_tree/binary_tree_bfs.java @@ -12,31 +12,31 @@ import java.util.*; public class binary_tree_bfs { /* レベル順走査 */ static List levelOrder(TreeNode root) { - // キューを初期化し、根ノードを追加 + // キューを初期化し、ルートノードを追加する Queue queue = new LinkedList<>(); queue.add(root); - // 走査順序を格納するリストを初期化 + // 走査順序を保存するためのリストを初期化する List list = new ArrayList<>(); while (!queue.isEmpty()) { - TreeNode node = queue.poll(); // キューのデキュー - list.add(node.val); // ノードの値を保存 + TreeNode node = queue.poll(); // デキュー + list.add(node.val); // ノードの値を保存する if (node.left != null) - queue.offer(node.left); // 左の子ノードをエンキュー + queue.offer(node.left); // 左子ノードをキューに追加 if (node.right != null) - queue.offer(node.right); // 右の子ノードをエンキュー + queue.offer(node.right); // 右子ノードをキューに追加 } return list; } public static void main(String[] args) { /* 二分木を初期化 */ - // 特定の関数を使用して配列を二分木に変換 + // ここでは、配列から直接二分木を生成する関数を利用する TreeNode root = TreeNode.listToTree(Arrays.asList(1, 2, 3, 4, 5, 6, 7)); System.out.println("\n二分木を初期化\n"); PrintUtil.printTree(root); /* レベル順走査 */ List list = levelOrder(root); - System.out.println("\nレベル順走査でのノードの出力順序 = " + list); + System.out.println("\nレベル順走査のノード出力シーケンス = " + list); } -} \ No newline at end of file +} diff --git a/ja/codes/java/chapter_tree/binary_tree_dfs.java b/ja/codes/java/chapter_tree/binary_tree_dfs.java index d35be32ee..6cf4404e0 100644 --- a/ja/codes/java/chapter_tree/binary_tree_dfs.java +++ b/ja/codes/java/chapter_tree/binary_tree_dfs.java @@ -13,11 +13,11 @@ public class binary_tree_dfs { // 走査順序を格納するリストを初期化 static ArrayList list = new ArrayList<>(); - /* 前順走査 */ + /* 先行順走査 */ static void preOrder(TreeNode root) { if (root == null) return; - // 訪問優先度: 根ノード -> 左部分木 -> 右部分木 + // 訪問順序:根ノード -> 左部分木 -> 右部分木 list.add(root.val); preOrder(root.left); preOrder(root.right); @@ -27,7 +27,7 @@ public class binary_tree_dfs { static void inOrder(TreeNode root) { if (root == null) return; - // 訪問優先度: 左部分木 -> 根ノード -> 右部分木 + // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 inOrder(root.left); list.add(root.val); inOrder(root.right); @@ -37,7 +37,7 @@ public class binary_tree_dfs { static void postOrder(TreeNode root) { if (root == null) return; - // 訪問優先度: 左部分木 -> 右部分木 -> 根ノード + // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード postOrder(root.left); postOrder(root.right); list.add(root.val); @@ -45,24 +45,24 @@ public class binary_tree_dfs { public static void main(String[] args) { /* 二分木を初期化 */ - // 特定の関数を使用して配列を二分木に変換 + // ここでは、配列から直接二分木を生成する関数を利用する TreeNode root = TreeNode.listToTree(Arrays.asList(1, 2, 3, 4, 5, 6, 7)); System.out.println("\n二分木を初期化\n"); PrintUtil.printTree(root); - /* 前順走査 */ + /* 先行順走査 */ list.clear(); preOrder(root); - System.out.println("\n前順走査でのノードの出力順序 = " + list); + System.out.println("\n先行順走査のノード出力シーケンス = " + list); /* 中順走査 */ list.clear(); inOrder(root); - System.out.println("\n中順走査でのノードの出力順序 = " + list); + System.out.println("\n中間順走査のノード出力シーケンス = " + list); /* 後順走査 */ list.clear(); postOrder(root); - System.out.println("\n後順走査でのノードの出力順序 = " + list); + System.out.println("\n後行順走査のノード出力シーケンス = " + list); } -} \ No newline at end of file +} diff --git a/ja/codes/java/utils/ListNode.java b/ja/codes/java/utils/ListNode.java index 8257ed35c..b36e6251e 100644 --- a/ja/codes/java/utils/ListNode.java +++ b/ja/codes/java/utils/ListNode.java @@ -15,7 +15,7 @@ public class ListNode { val = x; } - /* リストを連結リストにデシリアライズ */ + /* リストを連結リストにデシリアライズする */ public static ListNode arrToLinkedList(int[] arr) { ListNode dum = new ListNode(0); ListNode head = dum; @@ -25,4 +25,4 @@ public class ListNode { } return dum.next; } -} \ No newline at end of file +} diff --git a/ja/codes/java/utils/PrintUtil.java b/ja/codes/java/utils/PrintUtil.java index 0d6ff1895..e49fa61c7 100644 --- a/ja/codes/java/utils/PrintUtil.java +++ b/ja/codes/java/utils/PrintUtil.java @@ -19,7 +19,7 @@ class Trunk { }; public class PrintUtil { - /* 行列を印刷 (配列) */ + /* 行列を出力する(Array) */ public static void printMatrix(T[][] matrix) { System.out.println("["); for (T[] row : matrix) { @@ -28,7 +28,7 @@ public class PrintUtil { System.out.println("]"); } - /* 行列を印刷 (リスト) */ + /* 行列を出力する(List) */ public static void printMatrix(List> matrix) { System.out.println("["); for (List row : matrix) { @@ -37,7 +37,7 @@ public class PrintUtil { System.out.println("]"); } - /* 連結リストを印刷 */ + /* 連結リストを出力 */ public static void printLinkedList(ListNode head) { List list = new ArrayList<>(); while (head != null) { @@ -47,16 +47,16 @@ public class PrintUtil { System.out.println(String.join(" -> ", list)); } - /* 二分木を印刷 */ + /* 二分木を出力 */ public static void printTree(TreeNode root) { printTree(root, null, false); } /** - * 二分木を印刷 - * この木プリンターはTECHIE DELIGHTから借用 - * https://www.techiedelight.com/c-program-print-binary-tree/ - */ + * 二分木を出力 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ public static void printTree(TreeNode root, Trunk prev, boolean isRight) { if (root == null) { return; @@ -97,20 +97,20 @@ public class PrintUtil { System.out.print(p.str); } - /* ハッシュテーブルを印刷 */ + /* ハッシュテーブルを出力 */ public static void printHashMap(Map map) { for (Map.Entry kv : map.entrySet()) { System.out.println(kv.getKey() + " -> " + kv.getValue()); } } - /* ヒープを印刷 (優先度キュー) */ + /* ヒープ(優先度付きキュー)を出力する */ public static void printHeap(Queue queue) { List list = new ArrayList<>(queue); - System.out.print("ヒープの配列表現:"); + System.out.print("ヒープの配列表現:"); System.out.println(list); - System.out.println("ヒープの木表現:"); + System.out.println("ヒープの木構造表現:"); TreeNode root = TreeNode.listToTree(list); printTree(root); } -} \ No newline at end of file +} diff --git a/ja/codes/java/utils/TreeNode.java b/ja/codes/java/utils/TreeNode.java index b64df35ce..538fe12d8 100644 --- a/ja/codes/java/utils/TreeNode.java +++ b/ja/codes/java/utils/TreeNode.java @@ -11,7 +11,7 @@ import java.util.*; /* 二分木ノードクラス */ public class TreeNode { public int val; // ノード値 - public int height; // ノード高さ + public int height; // ノードの高さ public TreeNode left; // 左子ノードへの参照 public TreeNode right; // 右子ノードへの参照 @@ -20,23 +20,23 @@ public class TreeNode { val = x; } - // シリアライゼーション符号化ルールについては、次を参照: + // シリアライズの符号化規則は以下を参照: // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ - // 二分木の配列表現: + // 二分木の配列表現: // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] - // 二分木の連結リスト表現: - // /——— 15 - // /——— 7 - // /——— 3 - // | \——— 6 - // | \——— 12 + // 二分木の連結リスト表現: + // /——— 15 + // /——— 7 + // /——— 3 + // | \——— 6 + // | \——— 12 // ——— 1 - // \——— 2 - // | /——— 9 - // \——— 4 - // \——— 8 + // \——— 2 + // | /——— 9 + // \——— 4 + // \——— 8 - /* リストを二分木にデシリアライズ:再帰的 */ + /* リストを二分木にデシリアライズする: 再帰 */ private static TreeNode listToTreeDFS(List arr, int i) { if (i < 0 || i >= arr.size() || arr.get(i) == null) { return null; @@ -47,12 +47,12 @@ public class TreeNode { return root; } - /* リストを二分木にデシリアライズ */ + /* リストを二分木にデシリアライズする */ public static TreeNode listToTree(List arr) { return listToTreeDFS(arr, 0); } - /* 二分木をリストにシリアライズ:再帰的 */ + /* 二分木をリストにシリアライズする: 再帰 */ private static void treeToListDFS(TreeNode root, int i, List res) { if (root == null) return; @@ -64,10 +64,10 @@ public class TreeNode { treeToListDFS(root.right, 2 * i + 2, res); } - /* 二分木をリストにシリアライズ */ + /* 二分木をリストにシリアライズする */ public static List treeToList(TreeNode root) { List res = new ArrayList<>(); treeToListDFS(root, 0, res); return res; } -} \ No newline at end of file +} diff --git a/ja/codes/java/utils/Vertex.java b/ja/codes/java/utils/Vertex.java index fbd941b3d..726dbab4f 100644 --- a/ja/codes/java/utils/Vertex.java +++ b/ja/codes/java/utils/Vertex.java @@ -16,7 +16,7 @@ public class Vertex { this.val = val; } - /* 値のリストvalsを入力し、頂点のリストvetsを返す */ + /* 値リスト vals を入力し、頂点リスト vets を返す */ public static Vertex[] valsToVets(int[] vals) { Vertex[] vets = new Vertex[vals.length]; for (int i = 0; i < vals.length; i++) { @@ -25,7 +25,7 @@ public class Vertex { return vets; } - /* 頂点のリストvetsを入力し、値のリストvalsを返す */ + /* 頂点リスト vets を入力し、値リスト vals を返す */ public static List vetsToVals(List vets) { List vals = new ArrayList<>(); for (Vertex vet : vets) { @@ -33,4 +33,4 @@ public class Vertex { } return vals; } -} \ No newline at end of file +} diff --git a/ja/codes/javascript/.prettierrc b/ja/codes/javascript/.prettierrc new file mode 100644 index 000000000..3f4aa8cb6 --- /dev/null +++ b/ja/codes/javascript/.prettierrc @@ -0,0 +1,6 @@ +{ + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true +} diff --git a/ja/codes/javascript/chapter_array_and_linkedlist/array.js b/ja/codes/javascript/chapter_array_and_linkedlist/array.js new file mode 100644 index 000000000..4bbdbff88 --- /dev/null +++ b/ja/codes/javascript/chapter_array_and_linkedlist/array.js @@ -0,0 +1,97 @@ +/** + * File: array.js + * Created Time: 2022-11-27 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 要素へランダムアクセス */ +function randomAccess(nums) { + // 区間 [0, nums.length) からランダムに 1 つの数を選ぶ + const random_index = Math.floor(Math.random() * nums.length); + // ランダムな要素を取得して返す + const random_num = nums[random_index]; + return random_num; +} + +/* 配列長を拡張する */ +// JavaScript の Array は動的配列であり、直接拡張できます +// 学習しやすいよう、本関数では Array を長さ不変の配列として扱います +function extend(nums, enlarge) { + // 拡張後の長さを持つ配列を初期化する + const res = new Array(nums.length + enlarge).fill(0); + // 元の配列の全要素を新しい配列にコピー + for (let i = 0; i < nums.length; i++) { + res[i] = nums[i]; + } + // 拡張後の新しい配列を返す + return res; +} + +/* 配列の index 番目に要素 num を挿入 */ +function insert(nums, num, index) { + // インデックス index 以降の全要素を 1 つ後ろへ移動する + for (let i = nums.length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // index の要素に num を代入する + nums[index] = num; +} + +/* index の要素を削除する */ +function remove(nums, index) { + // インデックス index より後ろの全要素を 1 つ前へ移動する + for (let i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* 配列を走査 */ +function traverse(nums) { + let count = 0; + // インデックスで配列を走査 + for (let i = 0; i < nums.length; i++) { + count += nums[i]; + } + // 配列要素を直接走査 + for (const num of nums) { + count += num; + } +} + +/* 配列内で指定要素を探す */ +function find(nums, target) { + for (let i = 0; i < nums.length; i++) { + if (nums[i] === target) return i; + } + return -1; +} + +/* Driver Code */ +/* 配列を初期化 */ +const arr = new Array(5).fill(0); +console.log('配列 arr =', arr); +let nums = [1, 3, 2, 5, 4]; +console.log('配列 nums =', nums); + +/* ランダムアクセス */ +let random_num = randomAccess(nums); +console.log('nums からランダム要素を取得', random_num); + +/* 長さを拡張 */ +nums = extend(nums, 3); +console.log('配列の長さを 8 に拡張し,nums =', nums); + +/* 要素を挿入する */ +insert(nums, 6, 3); +console.log('インデックス 3 に数字 6 を挿入し,nums =', nums); + +/* 要素を削除 */ +remove(nums, 2); +console.log('インデックス 2 の要素を削除し,nums =', nums); + +/* 配列を走査 */ +traverse(nums); + +/* 要素を探索する */ +let index = find(nums, 3); +console.log('nums 内で要素 3 を検索し,インデックス =', index); diff --git a/ja/codes/javascript/chapter_array_and_linkedlist/linked_list.js b/ja/codes/javascript/chapter_array_and_linkedlist/linked_list.js new file mode 100644 index 000000000..1c5d3e4cf --- /dev/null +++ b/ja/codes/javascript/chapter_array_and_linkedlist/linked_list.js @@ -0,0 +1,82 @@ +/** + * File: linked_list.js + * Created Time: 2022-12-12 + * Author: IsChristina (christinaxia77@foxmail.com), Justin (xiefahit@gmail.com) + */ + +const { printLinkedList } = require('../modules/PrintUtil'); +const { ListNode } = require('../modules/ListNode'); + +/* 連結リストでノード n0 の後ろにノード P を挿入する */ +function insert(n0, P) { + const n1 = n0.next; + P.next = n1; + n0.next = P; +} + +/* 連結リストでノード n0 の直後のノードを削除する */ +function remove(n0) { + if (!n0.next) return; + // n0 -> P -> n1 + const P = n0.next; + const n1 = P.next; + n0.next = n1; +} + +/* 連結リスト内で index 番目のノードにアクセス */ +function access(head, index) { + for (let i = 0; i < index; i++) { + if (!head) { + return null; + } + head = head.next; + } + return head; +} + +/* 連結リストで値が target の最初のノードを探す */ +function find(head, target) { + let index = 0; + while (head !== null) { + if (head.val === target) { + return index; + } + head = head.next; + index += 1; + } + return -1; +} + +/* Driver Code */ +/* 連結リストを初期化 */ +// 各ノードを初期化 +const n0 = new ListNode(1); +const n1 = new ListNode(3); +const n2 = new ListNode(2); +const n3 = new ListNode(5); +const n4 = new ListNode(4); +// ノード間の参照を構築する +n0.next = n1; +n1.next = n2; +n2.next = n3; +n3.next = n4; +console.log('初期化された連結リストは'); +printLinkedList(n0); + +/* ノードを挿入 */ +insert(n0, new ListNode(0)); +console.log('ノード挿入後の連結リストは'); +printLinkedList(n0); + +/* ノードを削除 */ +remove(n0); +console.log('ノード削除後の連結リストは'); +printLinkedList(n0); + +/* ノードにアクセス */ +const node = access(n0, 3); +console.log('連結リストのインデックス 3 のノードの値 = ' + node.val); + +/* ノードを探索 */ +const index = find(n0, 2); +console.log('連結リスト内で値が 2 のノードのインデックス = ' + index); diff --git a/ja/codes/javascript/chapter_array_and_linkedlist/list.js b/ja/codes/javascript/chapter_array_and_linkedlist/list.js new file mode 100644 index 000000000..77d6ea94d --- /dev/null +++ b/ja/codes/javascript/chapter_array_and_linkedlist/list.js @@ -0,0 +1,57 @@ +/** + * File: list.js + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* リストを初期化 */ +const nums = [1, 3, 2, 5, 4]; +console.log(`リスト nums = ${nums}`); + +/* 要素にアクセス */ +const num = nums[1]; +console.log(`インデックス 1 の要素にアクセスし,num = ${num}`); + +/* 要素を更新 */ +nums[1] = 0; +console.log(`インデックス 1 の要素を 0 に更新し,nums = ${nums}`); + +/* リストを空にする */ +nums.length = 0; +console.log(`リストを空にした後,nums = ${nums}`); + +/* 末尾に要素を追加 */ +nums.push(1); +nums.push(3); +nums.push(2); +nums.push(5); +nums.push(4); +console.log(`要素追加後,nums = ${nums}`); + +/* 中間に要素を挿入 */ +nums.splice(3, 0, 6); +console.log(`インデックス 3 に数字 6 を挿入し,nums = ${nums}`); + +/* 要素を削除 */ +nums.splice(3, 1); +console.log(`インデックス 3 の要素を削除し,nums = ${nums}`); + +/* インデックスでリストを走査 */ +let count = 0; +for (let i = 0; i < nums.length; i++) { + count += nums[i]; +} +/* リスト要素を直接走査 */ +count = 0; +for (const x of nums) { + count += x; +} + +/* 2 つのリストを連結する */ +const nums1 = [6, 8, 7, 10, 9]; +nums.push(...nums1); +console.log(`リスト nums1 を nums の後ろに連結し,nums = ${nums}`); + +/* リストをソート */ +nums.sort((a, b) => a - b); +console.log(`リストをソート後,nums = ${nums}`); diff --git a/ja/codes/javascript/chapter_array_and_linkedlist/my_list.js b/ja/codes/javascript/chapter_array_and_linkedlist/my_list.js new file mode 100644 index 000000000..d83143324 --- /dev/null +++ b/ja/codes/javascript/chapter_array_and_linkedlist/my_list.js @@ -0,0 +1,141 @@ +/** + * File: my_list.js + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* リストクラス */ +class MyList { + #arr = new Array(); // 配列(リスト要素を格納) + #capacity = 10; // リスト容量 + #size = 0; // リストの長さ(現在の要素数) + #extendRatio = 2; // リスト拡張時の増加倍率 + + /* コンストラクタ */ + constructor() { + this.#arr = new Array(this.#capacity); + } + + /* リストの長さを取得(現在の要素数) */ + size() { + return this.#size; + } + + /* リスト容量を取得する */ + capacity() { + return this.#capacity; + } + + /* 要素にアクセス */ + get(index) { + // インデックスが範囲外なら例外を送出する。以下同様 + if (index < 0 || index >= this.#size) throw new Error('インデックスが範囲外です'); + return this.#arr[index]; + } + + /* 要素を更新 */ + set(index, num) { + if (index < 0 || index >= this.#size) throw new Error('インデックスが範囲外です'); + this.#arr[index] = num; + } + + /* 末尾に要素を追加 */ + add(num) { + // 長さが容量に等しい場合は拡張が必要 + if (this.#size === this.#capacity) { + this.extendCapacity(); + } + // 新しい要素をリストの末尾に追加する + this.#arr[this.#size] = num; + this.#size++; + } + + /* 中間に要素を挿入 */ + insert(index, num) { + if (index < 0 || index >= this.#size) throw new Error('インデックスが範囲外です'); + // 要素数が容量を超えると、拡張機構が発動する + if (this.#size === this.#capacity) { + this.extendCapacity(); + } + // index 以降の要素をすべて 1 つ後ろへずらす + for (let j = this.#size - 1; j >= index; j--) { + this.#arr[j + 1] = this.#arr[j]; + } + // 要素数を更新 + this.#arr[index] = num; + this.#size++; + } + + /* 要素を削除 */ + remove(index) { + if (index < 0 || index >= this.#size) throw new Error('インデックスが範囲外です'); + let num = this.#arr[index]; + // インデックス index より後の要素をすべて 1 つ前に移動する + for (let j = index; j < this.#size - 1; j++) { + this.#arr[j] = this.#arr[j + 1]; + } + // 要素数を更新 + this.#size--; + // 削除された要素を返す + return num; + } + + /* リストの拡張 */ + extendCapacity() { + // 元の配列の extendRatio 倍の長さを持つ新しい配列を作成し、元の配列をコピーする + this.#arr = this.#arr.concat( + new Array(this.capacity() * (this.#extendRatio - 1)) + ); + // リストの容量を更新 + this.#capacity = this.#arr.length; + } + + /* リストを配列に変換する */ + toArray() { + let size = this.size(); + // 有効長の範囲内のリスト要素のみを変換 + const arr = new Array(size); + for (let i = 0; i < size; i++) { + arr[i] = this.get(i); + } + return arr; + } +} + +/* Driver Code */ +/* リストを初期化 */ +const nums = new MyList(); +/* 末尾に要素を追加 */ +nums.add(1); +nums.add(3); +nums.add(2); +nums.add(5); +nums.add(4); +console.log( + `リスト nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長さ = ${nums.size()}` +); + +/* 中間に要素を挿入 */ +nums.insert(3, 6); +console.log(`インデックス 3 に数字 6 を挿入し,nums = ${nums.toArray()}`); + +/* 要素を削除 */ +nums.remove(3); +console.log(`インデックス 3 の要素を削除し,nums = ${nums.toArray()}`); + +/* 要素にアクセス */ +const num = nums.get(1); +console.log(`インデックス 1 の要素にアクセスし,num = ${num}`); + +/* 要素を更新 */ +nums.set(1, 0); +console.log(`インデックス 1 の要素を 0 に更新し,nums = ${nums.toArray()}`); + +/* 拡張機構をテストする */ +for (let i = 0; i < 10; i++) { + // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する + nums.add(i); +} +console.log( + `拡張後のリスト nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長さ = ${nums.size()}` +); diff --git a/ja/codes/javascript/chapter_backtracking/n_queens.js b/ja/codes/javascript/chapter_backtracking/n_queens.js new file mode 100644 index 000000000..f6b58b942 --- /dev/null +++ b/ja/codes/javascript/chapter_backtracking/n_queens.js @@ -0,0 +1,55 @@ +/** + * File: n_queens.js + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* バックトラッキング:N クイーン */ +function backtrack(row, n, state, res, cols, diags1, diags2) { + // すべての行への配置が完了したら、解を記録する + if (row === n) { + res.push(state.map((row) => row.slice())); + return; + } + // すべての列を走査 + for (let col = 0; col < n; col++) { + // このマスに対応する主対角線と副対角線を計算 + const diag1 = row - col + n - 1; + const 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; + } + } +} + +/* N クイーンを解く */ +function nQueens(n) { + // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す + const state = Array.from({ length: n }, () => Array(n).fill('#')); + const cols = Array(n).fill(false); // 列にクイーンがあるか記録 + const diags1 = Array(2 * n - 1).fill(false); // 主対角線にクイーンがあるかを記録 + const diags2 = Array(2 * n - 1).fill(false); // 副対角線にクイーンがあるかを記録 + const res = []; + + backtrack(0, n, state, res, cols, diags1, diags2); + return res; +} + +// Driver Code +const n = 4; +const res = nQueens(n); + +console.log(`入力する盤面の縦横は ${n}`); +console.log(`クイーン配置の解法は全部で ${res.length} 通り`); +res.forEach((state) => { + console.log('--------------------'); + state.forEach((row) => console.log(row)); +}); diff --git a/ja/codes/javascript/chapter_backtracking/permutations_i.js b/ja/codes/javascript/chapter_backtracking/permutations_i.js new file mode 100644 index 000000000..80ed884e8 --- /dev/null +++ b/ja/codes/javascript/chapter_backtracking/permutations_i.js @@ -0,0 +1,42 @@ +/** + * File: permutations_i.js + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* バックトラッキング:順列 I */ +function backtrack(state, choices, selected, res) { + // 状態の長さが要素数に等しければ、解を記録 + if (state.length === choices.length) { + res.push([...state]); + return; + } + // すべての選択肢を走査 + choices.forEach((choice, i) => { + // 枝刈り:要素の重複選択を許可しない + if (!selected[i]) { + // 試行: 選択を行い、状態を更新 + selected[i] = true; + state.push(choice); + // 次の選択へ進む + backtrack(state, choices, selected, res); + // バックトラック:選択を取り消し、前の状態に戻す + selected[i] = false; + state.pop(); + } + }); +} + +/* 全順列 I */ +function permutationsI(nums) { + const res = []; + backtrack([], nums, Array(nums.length).fill(false), res); + return res; +} + +// Driver Code +const nums = [1, 2, 3]; +const res = permutationsI(nums); + +console.log(`入力配列 nums = ${JSON.stringify(nums)}`); +console.log(`すべての順列 res = ${JSON.stringify(res)}`); diff --git a/ja/codes/javascript/chapter_backtracking/permutations_ii.js b/ja/codes/javascript/chapter_backtracking/permutations_ii.js new file mode 100644 index 000000000..29d180e23 --- /dev/null +++ b/ja/codes/javascript/chapter_backtracking/permutations_ii.js @@ -0,0 +1,44 @@ +/** + * File: permutations_ii.js + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* バックトラッキング:順列 II */ +function backtrack(state, choices, selected, res) { + // 状態の長さが要素数に等しければ、解を記録 + if (state.length === choices.length) { + res.push([...state]); + return; + } + // すべての選択肢を走査 + const duplicated = new Set(); + choices.forEach((choice, i) => { + // 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない + if (!selected[i] && !duplicated.has(choice)) { + // 試行: 選択を行い、状態を更新 + duplicated.add(choice); // 選択済みの要素値を記録 + selected[i] = true; + state.push(choice); + // 次の選択へ進む + backtrack(state, choices, selected, res); + // バックトラック:選択を取り消し、前の状態に戻す + selected[i] = false; + state.pop(); + } + }); +} + +/* 全順列 II */ +function permutationsII(nums) { + const res = []; + backtrack([], nums, Array(nums.length).fill(false), res); + return res; +} + +// Driver Code +const nums = [1, 2, 2]; +const res = permutationsII(nums); + +console.log(`入力配列 nums = ${JSON.stringify(nums)}`); +console.log(`すべての順列 res = ${JSON.stringify(res)}`); diff --git a/ja/codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js b/ja/codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js new file mode 100644 index 000000000..27e8ed003 --- /dev/null +++ b/ja/codes/javascript/chapter_backtracking/preorder_traversal_i_compact.js @@ -0,0 +1,33 @@ +/** + * File: preorder_traversal_i_compact.js + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 前順走査:例題 1 */ +function preOrder(root, res) { + if (root === null) { + return; + } + if (root.val === 7) { + // 解を記録 + res.push(root); + } + preOrder(root.left, res); + preOrder(root.right, res); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n二分木を初期化'); +printTree(root); + +// 先行順走査 +const res = []; +preOrder(root, res); + +console.log('\n値が 7 のすべてのノードを出力'); +console.log(res.map((node) => node.val)); diff --git a/ja/codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js b/ja/codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js new file mode 100644 index 000000000..b0dd8e5f8 --- /dev/null +++ b/ja/codes/javascript/chapter_backtracking/preorder_traversal_ii_compact.js @@ -0,0 +1,40 @@ +/** + * File: preorder_traversal_ii_compact.js + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 前順走査:例題 2 */ +function preOrder(root, path, res) { + if (root === null) { + return; + } + // 試す + path.push(root); + if (root.val === 7) { + // 解を記録 + res.push([...path]); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // バックトラック + path.pop(); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n二分木を初期化'); +printTree(root); + +// 先行順走査 +const path = []; +const res = []; +preOrder(root, path, res); + +console.log('\n根ノードからノード 7 までのすべての経路を出力'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); diff --git a/ja/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js b/ja/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js new file mode 100644 index 000000000..fcf96677a --- /dev/null +++ b/ja/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js @@ -0,0 +1,41 @@ +/** + * File: preorder_traversal_iii_compact.js + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 前順走査:例題 3 */ +function preOrder(root, path, res) { + // 枝刈り + if (root === null || root.val === 3) { + return; + } + // 試す + path.push(root); + if (root.val === 7) { + // 解を記録 + res.push([...path]); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // バックトラック + path.pop(); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n二分木を初期化'); +printTree(root); + +// 先行順走査 +const path = []; +const res = []; +preOrder(root, path, res); + +console.log('\n根ノードからノード 7 までのすべての経路を出力し,経路には値が 3 のノードを含まない'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); diff --git a/ja/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js b/ja/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js new file mode 100644 index 000000000..415e3b481 --- /dev/null +++ b/ja/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js @@ -0,0 +1,68 @@ +/** + * File: preorder_traversal_iii_template.js + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 現在の状態が解かどうかを判定 */ +function isSolution(state) { + return state && state[state.length - 1]?.val === 7; +} + +/* 解を記録 */ +function recordSolution(state, res) { + res.push([...state]); +} + +/* 現在の状態で、この選択が有効かどうかを判定 */ +function isValid(state, choice) { + return choice !== null && choice.val !== 3; +} + +/* 状態を更新 */ +function makeChoice(state, choice) { + state.push(choice); +} + +/* 状態を元に戻す */ +function undoChoice(state) { + state.pop(); +} + +/* バックトラッキング:例題 3 */ +function backtrack(state, choices, res) { + // 解かどうかを確認 + if (isSolution(state)) { + // 解を記録 + recordSolution(state, res); + } + // すべての選択肢を走査 + for (const choice of choices) { + // 枝刈り:選択が妥当かを確認する + if (isValid(state, choice)) { + // 試行: 選択を行い、状態を更新 + makeChoice(state, choice); + // 次の選択へ進む + backtrack(state, [choice.left, choice.right], res); + // バックトラック:選択を取り消し、前の状態に戻す + undoChoice(state); + } + } +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n二分木を初期化'); +printTree(root); + +// バックトラッキング法 +const res = []; +backtrack([], [root], res); + +console.log('\n根ノードからノード 7 までのすべての経路を出力し,経路には値が 3 のノードを含まないことを条件とする'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); diff --git a/ja/codes/javascript/chapter_backtracking/subset_sum_i.js b/ja/codes/javascript/chapter_backtracking/subset_sum_i.js new file mode 100644 index 000000000..9d2638900 --- /dev/null +++ b/ja/codes/javascript/chapter_backtracking/subset_sum_i.js @@ -0,0 +1,46 @@ +/** + * File: subset_sum_i.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* バックトラッキング:部分和 I */ +function backtrack(state, target, choices, start, res) { + // 部分集合の和が target に等しければ、解を記録 + if (target === 0) { + res.push([...state]); + return; + } + // すべての選択肢を走査 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + for (let i = start; i < choices.length; i++) { + // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため + if (target - choices[i] < 0) { + break; + } + // 試す:選択を行い、target と start を更新 + state.push(choices[i]); + // 次の選択へ進む + backtrack(state, target - choices[i], choices, i, res); + // バックトラック:選択を取り消し、前の状態に戻す + state.pop(); + } +} + +/* 部分和 I を解く */ +function subsetSumI(nums, target) { + const state = []; // 状態(部分集合) + nums.sort((a, b) => a - b); // nums をソート + const start = 0; // 開始点を走査 + const res = []; // 結果リスト(部分集合のリスト) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +const nums = [3, 4, 5]; +const target = 9; +const res = subsetSumI(nums, target); +console.log(`入力配列 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`和が ${target} に等しいすべての部分集合 res = ${JSON.stringify(res)}`); diff --git a/ja/codes/javascript/chapter_backtracking/subset_sum_i_naive.js b/ja/codes/javascript/chapter_backtracking/subset_sum_i_naive.js new file mode 100644 index 000000000..de878fdc4 --- /dev/null +++ b/ja/codes/javascript/chapter_backtracking/subset_sum_i_naive.js @@ -0,0 +1,44 @@ +/** + * File: subset_sum_i_naive.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* バックトラッキング:部分和 I */ +function backtrack(state, target, total, choices, res) { + // 部分集合の和が target に等しければ、解を記録 + if (total === target) { + res.push([...state]); + return; + } + // すべての選択肢を走査 + for (let i = 0; i < choices.length; i++) { + // 枝刈り:部分和が target を超える場合はその選択をスキップする + if (total + choices[i] > target) { + continue; + } + // 試行:選択を行い、要素と total を更新する + state.push(choices[i]); + // 次の選択へ進む + backtrack(state, target, total + choices[i], choices, res); + // バックトラック:選択を取り消し、前の状態に戻す + state.pop(); + } +} + +/* 部分和 I を解く(重複部分集合を含む) */ +function subsetSumINaive(nums, target) { + const state = []; // 状態(部分集合) + const total = 0; // 部分和 + const res = []; // 結果リスト(部分集合のリスト) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +const nums = [3, 4, 5]; +const target = 9; +const res = subsetSumINaive(nums, target); +console.log(`入力配列 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`和が ${target} に等しいすべての部分集合 res = ${JSON.stringify(res)}`); +console.log('注意してください。この方法の出力結果には重複した集合が含まれます'); diff --git a/ja/codes/javascript/chapter_backtracking/subset_sum_ii.js b/ja/codes/javascript/chapter_backtracking/subset_sum_ii.js new file mode 100644 index 000000000..caf125f44 --- /dev/null +++ b/ja/codes/javascript/chapter_backtracking/subset_sum_ii.js @@ -0,0 +1,51 @@ +/** + * File: subset_sum_ii.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* バックトラッキング:部分和 II */ +function backtrack(state, target, choices, start, res) { + // 部分集合の和が target に等しければ、解を記録 + if (target === 0) { + res.push([...state]); + return; + } + // すべての選択肢を走査 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける + for (let i = start; i < choices.length; i++) { + // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため + if (target - choices[i] < 0) { + break; + } + // 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする + if (i > start && choices[i] === choices[i - 1]) { + continue; + } + // 試す:選択を行い、target と start を更新 + state.push(choices[i]); + // 次の選択へ進む + backtrack(state, target - choices[i], choices, i + 1, res); + // バックトラック:選択を取り消し、前の状態に戻す + state.pop(); + } +} + +/* 部分和 II を解く */ +function subsetSumII(nums, target) { + const state = []; // 状態(部分集合) + nums.sort((a, b) => a - b); // nums をソート + const start = 0; // 開始点を走査 + const res = []; // 結果リスト(部分集合のリスト) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +const nums = [4, 4, 5]; +const target = 9; +const res = subsetSumII(nums, target); +console.log(`入力配列 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`和が ${target} に等しいすべての部分集合 res = ${JSON.stringify(res)}`); diff --git a/ja/codes/javascript/chapter_computational_complexity/iteration.js b/ja/codes/javascript/chapter_computational_complexity/iteration.js new file mode 100644 index 000000000..6a29ccc9e --- /dev/null +++ b/ja/codes/javascript/chapter_computational_complexity/iteration.js @@ -0,0 +1,70 @@ +/** + * File: iteration.js + * Created Time: 2023-08-28 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* for ループ */ +function forLoop(n) { + let res = 0; + // 1, 2, ..., n-1, n を順に加算する + for (let i = 1; i <= n; i++) { + res += i; + } + return res; +} + +/* while ループ */ +function whileLoop(n) { + let res = 0; + let i = 1; // 条件変数を初期化する + // 1, 2, ..., n-1, n を順に加算する + while (i <= n) { + res += i; + i++; // 条件変数を更新する + } + return res; +} + +/* while ループ(2回更新) */ +function whileLoopII(n) { + let res = 0; + let i = 1; // 条件変数を初期化する + // 1, 4, 10, ... を順に加算する + while (i <= n) { + res += i; + // 条件変数を更新する + i++; + i *= 2; + } + return res; +} + +/* 二重 for ループ */ +function nestedForLoop(n) { + let res = ''; + // i = 1, 2, ..., n-1, n とループする + for (let i = 1; i <= n; i++) { + // j = 1, 2, ..., n-1, n とループする + for (let j = 1; j <= n; j++) { + res += `(${i}, ${j}), `; + } + } + return res; +} + +/* Driver Code */ +const n = 5; +let res; + +res = forLoop(n); +console.log(`for ループの合計結果 res = ${res}`); + +res = whileLoop(n); +console.log(`while ループの合計結果 res = ${res}`); + +res = whileLoopII(n); +console.log(`while ループ(2 回更新)の合計結果 res = ${res}`); + +const resStr = nestedForLoop(n); +console.log(`二重 for ループの走査結果 ${resStr}`); diff --git a/ja/codes/javascript/chapter_computational_complexity/recursion.js b/ja/codes/javascript/chapter_computational_complexity/recursion.js new file mode 100644 index 000000000..76e29ffef --- /dev/null +++ b/ja/codes/javascript/chapter_computational_complexity/recursion.js @@ -0,0 +1,69 @@ +/** + * File: recursion.js + * Created Time: 2023-08-28 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 再帰 */ +function recur(n) { + // 終了条件 + if (n === 1) return 1; + // 再帰:再帰呼び出し + const res = recur(n - 1); + // 帰りがけ:結果を返す + return n + res; +} + +/* 反復で再帰を模擬する */ +function forLoopRecur(n) { + // 明示的なスタックを使ってシステムコールスタックを模擬する + const stack = []; + let res = 0; + // 再帰:再帰呼び出し + for (let i = n; i > 0; i--) { + // 「スタックへのプッシュ」で「再帰」を模擬する + stack.push(i); + } + // 帰りがけ:結果を返す + while (stack.length) { + // 「スタックから取り出す操作」で「帰り」をシミュレート + res += stack.pop(); + } + // res = 1+2+3+...+n + return res; +} + +/* 末尾再帰 */ +function tailRecur(n, res) { + // 終了条件 + if (n === 0) return res; + // 末尾再帰呼び出し + return tailRecur(n - 1, res + n); +} + +/* フィボナッチ数列:再帰 */ +function fib(n) { + // 終了条件 f(1) = 0, f(2) = 1 + if (n === 1 || n === 2) return n - 1; + // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す + const res = fib(n - 1) + fib(n - 2); + // 結果 f(n) を返す + return res; +} + +/* Driver Code */ +const n = 5; +let res; + +res = recur(n); +console.log(`再帰関数の合計結果 res = ${res}`); + +res = forLoopRecur(n); +console.log(`反復で再帰を模擬した合計結果 res = ${res}`); + +res = tailRecur(n, 0); +console.log(`末尾再帰関数の合計結果 res = ${res}`); + +res = fib(n); +console.log(`フィボナッチ数列の第 ${n} 項は ${res}`); + diff --git a/ja/codes/javascript/chapter_computational_complexity/space_complexity.js b/ja/codes/javascript/chapter_computational_complexity/space_complexity.js new file mode 100644 index 000000000..d4c8e5451 --- /dev/null +++ b/ja/codes/javascript/chapter_computational_complexity/space_complexity.js @@ -0,0 +1,103 @@ +/** + * File: space_complexity.js + * Created Time: 2023-02-05 + * Author: Justin (xiefahit@gmail.com) + */ + +const { ListNode } = require('../modules/ListNode'); +const { TreeNode } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 関数 */ +function constFunc() { + // 何らかの処理を行う + return 0; +} + +/* 定数階 */ +function constant(n) { + // 定数、変数、オブジェクトは O(1) の空間を占める + const a = 0; + const b = 0; + const nums = new Array(10000); + const node = new ListNode(0); + // ループ内の変数は O(1) の空間を占める + for (let i = 0; i < n; i++) { + const c = 0; + } + // ループ内の関数は O(1) の空間を占める + for (let i = 0; i < n; i++) { + constFunc(); + } +} + +/* 線形階 */ +function linear(n) { + // 長さ n の配列は O(n) の空間を使用 + const nums = new Array(n); + // 長さ n のリストは O(n) の空間を使用 + const nodes = []; + for (let i = 0; i < n; i++) { + nodes.push(new ListNode(i)); + } + // 長さ n のハッシュテーブルは O(n) の空間を使用 + const map = new Map(); + for (let i = 0; i < n; i++) { + map.set(i, i.toString()); + } +} + +/* 線形時間(再帰実装) */ +function linearRecur(n) { + console.log(`再帰 n = ${n}`); + if (n === 1) return; + linearRecur(n - 1); +} + +/* 二乗階 */ +function quadratic(n) { + // 行列は O(n^2) の空間を使用する + const numMatrix = Array(n) + .fill(null) + .map(() => Array(n).fill(null)); + // 二次元リストは O(n^2) の空間を使用 + const numList = []; + for (let i = 0; i < n; i++) { + const tmp = []; + for (let j = 0; j < n; j++) { + tmp.push(0); + } + numList.push(tmp); + } +} + +/* 二次時間(再帰実装) */ +function quadraticRecur(n) { + if (n <= 0) return 0; + const nums = new Array(n); + console.log(`再帰 n = ${n} における nums の長さ = ${nums.length}`); + return quadraticRecur(n - 1); +} + +/* 指数時間(完全二分木の構築) */ +function buildTree(n) { + if (n === 0) return null; + const root = new TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +const n = 5; +// 定数階 +constant(n); +// 線形階 +linear(n); +linearRecur(n); +// 二乗階 +quadratic(n); +quadraticRecur(n); +// 指数オーダー +const root = buildTree(n); +printTree(root); diff --git a/ja/codes/javascript/chapter_computational_complexity/time_complexity.js b/ja/codes/javascript/chapter_computational_complexity/time_complexity.js new file mode 100644 index 000000000..24048bd37 --- /dev/null +++ b/ja/codes/javascript/chapter_computational_complexity/time_complexity.js @@ -0,0 +1,155 @@ +/** + * File: time_complexity.js + * Created Time: 2023-01-02 + * Author: RiverTwilight (contact@rene.wang) + */ + +/* 定数階 */ +function constant(n) { + let count = 0; + const size = 100000; + for (let i = 0; i < size; i++) count++; + return count; +} + +/* 線形階 */ +function linear(n) { + let count = 0; + for (let i = 0; i < n; i++) count++; + return count; +} + +/* 線形時間(配列を走査) */ +function arrayTraversal(nums) { + let count = 0; + // ループ回数は配列長に比例する + for (let i = 0; i < nums.length; i++) { + count++; + } + return count; +} + +/* 二乗階 */ +function quadratic(n) { + let count = 0; + // ループ回数はデータサイズ n の二乗に比例する + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* 二次時間(バブルソート) */ +function bubbleSort(nums) { + let count = 0; // カウンタ + // 外側のループ:未ソート区間は [0, i] + for (let i = nums.length - 1; i > 0; i--) { + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // nums[j] と nums[j + 1] を交換 + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 要素交換には 3 回の単位操作が含まれる + } + } + } + return count; +} + +/* 指数時間(ループ実装) */ +function exponential(n) { + let count = 0, + base = 1; + // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する + for (let i = 0; i < n; i++) { + for (let j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* 指数時間(再帰実装) */ +function expRecur(n) { + if (n === 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* 対数時間(ループ実装) */ +function logarithmic(n) { + let count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; +} + +/* 対数時間(再帰実装) */ +function logRecur(n) { + if (n <= 1) return 0; + return logRecur(n / 2) + 1; +} + +/* 線形対数時間 */ +function linearLogRecur(n) { + if (n <= 1) return 1; + let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); + for (let i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 階乗時間(再帰実装) */ +function factorialRecur(n) { + if (n === 0) return 1; + let count = 0; + // 1個から n 個に分裂 + for (let i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +// n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる +const n = 8; +console.log('入力データサイズ n = ' + n); + +let count = constant(n); +console.log('定数時間の操作回数 = ' + count); + +count = linear(n); +console.log('線形時間の操作回数 = ' + count); +count = arrayTraversal(new Array(n)); +console.log('線形時間(配列の走査)の操作回数 = ' + count); + +count = quadratic(n); +console.log('二乗時間の操作回数 = ' + count); +let nums = new Array(n); +for (let i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] +count = bubbleSort(nums); +console.log('二乗時間(バブルソート)の操作回数 = ' + count); + +count = exponential(n); +console.log('指数時間(ループ実装)の操作回数 = ' + count); +count = expRecur(n); +console.log('指数時間(再帰実装)の操作回数 = ' + count); + +count = logarithmic(n); +console.log('対数時間(ループ実装)の操作回数 = ' + count); +count = logRecur(n); +console.log('対数時間(再帰実装)の操作回数 = ' + count); + +count = linearLogRecur(n); +console.log('線形対数時間(再帰実装)の操作回数 = ' + count); + +count = factorialRecur(n); +console.log('階乗時間(再帰実装)の操作回数 = ' + count); diff --git a/ja/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js b/ja/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js new file mode 100644 index 000000000..cbb5788d0 --- /dev/null +++ b/ja/codes/javascript/chapter_computational_complexity/worst_best_time_complexity.js @@ -0,0 +1,43 @@ +/** + * File: worst_best_time_complexity.js + * Created Time: 2023-01-05 + * Author: RiverTwilight (contact@rene.wang) + */ + +/* 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 */ +function randomNumbers(n) { + const nums = Array(n); + // 配列 nums = { 1, 2, 3, ..., n } を生成 + for (let i = 0; i < n; i++) { + nums[i] = i + 1; + } + // 配列要素をランダムにシャッフル + for (let i = 0; i < n; i++) { + const r = Math.floor(Math.random() * (i + 1)); + const temp = nums[i]; + nums[i] = nums[r]; + nums[r] = temp; + } + return nums; +} + +/* 配列 nums 内で数値 1 のインデックスを探す */ +function findOne(nums) { + for (let i = 0; i < nums.length; i++) { + // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる + // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる + if (nums[i] === 1) { + return i; + } + } + return -1; +} + +/* Driver Code */ +for (let i = 0; i < 10; i++) { + const n = 100; + const nums = randomNumbers(n); + const index = findOne(nums); + console.log('\n配列 [ 1, 2, ..., n ] をシャッフルした後 = [' + nums.join(', ') + ']'); + console.log('数字 1 のインデックスは ' + index); +} diff --git a/ja/codes/javascript/chapter_divide_and_conquer/binary_search_recur.js b/ja/codes/javascript/chapter_divide_and_conquer/binary_search_recur.js new file mode 100644 index 000000000..e7dc17c34 --- /dev/null +++ b/ja/codes/javascript/chapter_divide_and_conquer/binary_search_recur.js @@ -0,0 +1,39 @@ +/** + * File: binary_search_recur.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 二分探索:問題 f(i, j) */ +function dfs(nums, target, i, j) { + // 区間が空なら対象要素は存在しないので -1 を返す + if (i > j) { + return -1; + } + // 中点インデックス m を計算 + const m = i + ((j - i) >> 1); + if (nums[m] < target) { + // 部分問題 f(m+1, j) を再帰的に解く + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // 部分問題 f(i, m-1) を再帰的に解く + return dfs(nums, target, i, m - 1); + } else { + // 目標要素が見つかったらそのインデックスを返す + return m; + } +} + +/* 二分探索 */ +function binarySearch(nums, target) { + const n = nums.length; + // 問題 f(0, n-1) を解く + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +const target = 6; +const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; +// 二分探索(両閉区間) +const index = binarySearch(nums, target); +console.log(`対象要素 6 のインデックス = ${index}`); diff --git a/ja/codes/javascript/chapter_divide_and_conquer/build_tree.js b/ja/codes/javascript/chapter_divide_and_conquer/build_tree.js new file mode 100644 index 000000000..e431fa85c --- /dev/null +++ b/ja/codes/javascript/chapter_divide_and_conquer/build_tree.js @@ -0,0 +1,44 @@ +/** + * File: build_tree.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +const { printTree } = require('../modules/PrintUtil'); +const { TreeNode } = require('../modules/TreeNode'); + +/* 二分木を構築:分割統治 */ +function dfs(preorder, inorderMap, i, l, r) { + // 部分木区間が空なら終了する + if (r - l < 0) return null; + // ルートノードを初期化する + const root = new TreeNode(preorder[i]); + // m を求めて左右部分木を分割する + const m = inorderMap.get(preorder[i]); + // 部分問題:左部分木を構築する + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // 部分問題:右部分木を構築する + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 根ノードを返す + return root; +} + +/* 二分木を構築 */ +function buildTree(preorder, inorder) { + // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する + let inorderMap = new Map(); + for (let i = 0; i < inorder.length; i++) { + inorderMap.set(inorder[i], i); + } + const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; +} + +/* Driver Code */ +const preorder = [3, 9, 2, 1, 7]; +const inorder = [9, 3, 1, 2, 7]; +console.log('前順走査 = ' + JSON.stringify(preorder)); +console.log('中順走査 = ' + JSON.stringify(inorder)); +const root = buildTree(preorder, inorder); +console.log('構築した二分木は:'); +printTree(root); diff --git a/ja/codes/javascript/chapter_divide_and_conquer/hanota.js b/ja/codes/javascript/chapter_divide_and_conquer/hanota.js new file mode 100644 index 000000000..18ef0f136 --- /dev/null +++ b/ja/codes/javascript/chapter_divide_and_conquer/hanota.js @@ -0,0 +1,52 @@ +/** + * File: hanota.js + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 円盤を 1 枚移動 */ +function move(src, tar) { + // src の上から円盤を1枚取り出す + const pan = src.pop(); + // 円盤を tar の上に置く + tar.push(pan); +} + +/* ハノイの塔の問題 f(i) を解く */ +function dfs(i, src, buf, tar) { + // src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す + if (i === 1) { + move(src, tar); + return; + } + // 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す + dfs(i - 1, src, tar, buf); + // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す + move(src, tar); + // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す + dfs(i - 1, buf, src, tar); +} + +/* ハノイの塔を解く */ +function solveHanota(A, B, C) { + const n = A.length; + // A の上から n 枚の円盤を B を介して C へ移す + dfs(n, A, B, C); +} + +/* Driver Code */ +// リスト末尾が柱の頂上 +const A = [5, 4, 3, 2, 1]; +const B = []; +const C = []; +console.log('初期状態:'); +console.log(`A = ${JSON.stringify(A)}`); +console.log(`B = ${JSON.stringify(B)}`); +console.log(`C = ${JSON.stringify(C)}`); + +solveHanota(A, B, C); + +console.log('円盤の移動完了後:'); +console.log(`A = ${JSON.stringify(A)}`); +console.log(`B = ${JSON.stringify(B)}`); +console.log(`C = ${JSON.stringify(C)}`); diff --git a/ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js b/ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js new file mode 100644 index 000000000..9fd8b48ab --- /dev/null +++ b/ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_backtrack.js @@ -0,0 +1,34 @@ +/** + * File: climbing_stairs_backtrack.js + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* バックトラッキング */ +function backtrack(choices, state, n, res) { + // 第 n 段に到達したら、方法数を 1 増やす + if (state === n) res.set(0, res.get(0) + 1); + // すべての選択肢を走査 + for (const choice of choices) { + // 枝刈り: 第 n 段を超えないようにする + if (state + choice > n) continue; + // 試行: 選択を行い、状態を更新 + backtrack(choices, state + choice, n, res); + // バックトラック + } +} + +/* 階段登り:バックトラッキング */ +function climbingStairsBacktrack(n) { + const choices = [1, 2]; // 1 段または 2 段上ることを選べる + const state = 0; // 第 0 段から上り始める + const res = new Map(); + res.set(0, 0); // res[0] を使って方法数を記録する + backtrack(choices, state, n, res); + return res.get(0); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsBacktrack(n); +console.log(`${n} 階の階段を上る方法は全部で ${res} 通り`); diff --git a/ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js b/ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js new file mode 100644 index 000000000..7d4cbbdc1 --- /dev/null +++ b/ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_constraint_dp.js @@ -0,0 +1,30 @@ +/** + * File: climbing_stairs_constraint_dp.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 制約付き階段登り:動的計画法 */ +function climbingStairsConstraintDP(n) { + if (n === 1 || n === 2) { + return 1; + } + // 部分問題の解を保存するために dp テーブルを初期化 + const dp = Array.from(new Array(n + 1), () => new Array(3)); + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for (let i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsConstraintDP(n); +console.log(`${n} 階の階段を上る方法は全部で ${res} 通り`); diff --git a/ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js b/ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js new file mode 100644 index 000000000..a2cfca3a8 --- /dev/null +++ b/ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs.js @@ -0,0 +1,24 @@ +/** + * File: climbing_stairs_dfs.js + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 検索 */ +function dfs(i) { + // dp[1] と dp[2] は既知なので返す + if (i === 1 || i === 2) return i; + // dp[i] = dp[i-1] + dp[i-2] + const count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* 階段登り:探索 */ +function climbingStairsDFS(n) { + return dfs(n); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsDFS(n); +console.log(`${n} 階の階段を上る方法は全部で ${res} 通り`); diff --git a/ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js b/ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js new file mode 100644 index 000000000..c150bd819 --- /dev/null +++ b/ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_dfs_mem.js @@ -0,0 +1,30 @@ +/** + * File: climbing_stairs_dfs_mem.js + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* メモ化探索 */ +function dfs(i, mem) { + // dp[1] と dp[2] は既知なので返す + if (i === 1 || i === 2) return i; + // dp[i] の記録があれば、それをそのまま返す + if (mem[i] != -1) return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + const count = dfs(i - 1, mem) + dfs(i - 2, mem); + // dp[i] を記録する + mem[i] = count; + return count; +} + +/* 階段登り:メモ化探索 */ +function climbingStairsDFSMem(n) { + // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す + const mem = new Array(n + 1).fill(-1); + return dfs(n, mem); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsDFSMem(n); +console.log(`${n} 階の階段を上る方法は全部で ${res} 通り`); diff --git a/ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js b/ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js new file mode 100644 index 000000000..6acde80bd --- /dev/null +++ b/ja/codes/javascript/chapter_dynamic_programming/climbing_stairs_dp.js @@ -0,0 +1,40 @@ +/** + * File: climbing_stairs_dp.js + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 階段登り:動的計画法 */ +function climbingStairsDP(n) { + if (n === 1 || n === 2) return n; + // 部分問題の解を保存するために dp テーブルを初期化 + const dp = new Array(n + 1).fill(-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]; +} + +/* 階段登り:空間最適化した動的計画法 */ +function climbingStairsDPComp(n) { + if (n === 1 || n === 2) return n; + let a = 1, + b = 2; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +const n = 9; +let res = climbingStairsDP(n); +console.log(`${n} 階の階段を上る方法は全部で ${res} 通り`); +res = climbingStairsDPComp(n); +console.log(`${n} 階の階段を上る方法は全部で ${res} 通り`); diff --git a/ja/codes/javascript/chapter_dynamic_programming/coin_change.js b/ja/codes/javascript/chapter_dynamic_programming/coin_change.js new file mode 100644 index 000000000..451437c9f --- /dev/null +++ b/ja/codes/javascript/chapter_dynamic_programming/coin_change.js @@ -0,0 +1,66 @@ +/** + * File: coin_change.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* コイン両替:動的計画法 */ +function coinChangeDP(coins, amt) { + const n = coins.length; + const MAX = amt + 1; + // dp テーブルを初期化 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // 状態遷移:先頭行と先頭列 + for (let a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 状態遷移: 残りの行と列 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[i][a] = dp[i - 1][a]; + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] !== MAX ? dp[n][amt] : -1; +} + +/* コイン交換:空間最適化後の動的計画法 */ +function coinChangeDPComp(coins, amt) { + const n = coins.length; + const MAX = amt + 1; + // dp テーブルを初期化 + const dp = Array.from({ length: amt + 1 }, () => MAX); + dp[0] = 0; + // 状態遷移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a]; + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] !== MAX ? dp[amt] : -1; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 4; + +// 動的計画法 +let res = coinChangeDP(coins, amt); +console.log(`目標金額を作るのに必要な最小硬貨枚数は ${res}`); + +// 空間最適化後の動的計画法 +res = coinChangeDPComp(coins, amt); +console.log(`目標金額を作るのに必要な最小硬貨枚数は ${res}`); diff --git a/ja/codes/javascript/chapter_dynamic_programming/coin_change_ii.js b/ja/codes/javascript/chapter_dynamic_programming/coin_change_ii.js new file mode 100644 index 000000000..4bc3bbc93 --- /dev/null +++ b/ja/codes/javascript/chapter_dynamic_programming/coin_change_ii.js @@ -0,0 +1,64 @@ +/** + * File: coin_change_ii.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* コイン両替 II:動的計画法 */ +function coinChangeIIDP(coins, amt) { + const n = coins.length; + // dp テーブルを初期化 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // 先頭列を初期化する + for (let i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 状態遷移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[i][a] = dp[i - 1][a]; + } else { + // コイン i を選ばない場合と選ぶ場合の和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; +} + +/* コイン両替 II:空間最適化した動的計画法 */ +function coinChangeIIDPComp(coins, amt) { + const n = coins.length; + // dp テーブルを初期化 + const dp = Array.from({ length: amt + 1 }, () => 0); + dp[0] = 1; + // 状態遷移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a]; + } else { + // コイン i を選ばない場合と選ぶ場合の和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 5; + +// 動的計画法 +let res = coinChangeIIDP(coins, amt); +console.log(`目標金額を作る硬貨の組み合わせ数は ${res}`); + +// 空間最適化後の動的計画法 +res = coinChangeIIDPComp(coins, amt); +console.log(`目標金額を作る硬貨の組み合わせ数は ${res}`); diff --git a/ja/codes/javascript/chapter_dynamic_programming/edit_distance.js b/ja/codes/javascript/chapter_dynamic_programming/edit_distance.js new file mode 100644 index 000000000..2e51ee738 --- /dev/null +++ b/ja/codes/javascript/chapter_dynamic_programming/edit_distance.js @@ -0,0 +1,135 @@ +/** + * File: edit_distance.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 編集距離:総当たり探索 */ +function editDistanceDFS(s, t, i, j) { + // s と t がともに空なら 0 を返す + if (i === 0 && j === 0) return 0; + + // s が空なら t の長さを返す + if (i === 0) return j; + + // t が空なら s の長さを返す + if (j === 0) return i; + + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if (s.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFS(s, t, i - 1, j - 1); + + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + const insert = editDistanceDFS(s, t, i, j - 1); + const del = editDistanceDFS(s, t, i - 1, j); + const replace = editDistanceDFS(s, t, i - 1, j - 1); + // 最小編集回数を返す + return Math.min(insert, del, replace) + 1; +} + +/* 編集距離:メモ化探索 */ +function editDistanceDFSMem(s, t, mem, i, j) { + // s と t がともに空なら 0 を返す + if (i === 0 && j === 0) return 0; + + // s が空なら t の長さを返す + if (i === 0) return j; + + // t が空なら s の長さを返す + if (j === 0) return i; + + // 記録済みなら、それをそのまま返す + if (mem[i][j] !== -1) return mem[i][j]; + + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if (s.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + const insert = editDistanceDFSMem(s, t, mem, i, j - 1); + const del = editDistanceDFSMem(s, t, mem, i - 1, j); + const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 最小編集回数を記録して返す + mem[i][j] = Math.min(insert, del, replace) + 1; + return mem[i][j]; +} + +/* 編集距離:動的計画法 */ +function editDistanceDP(s, t) { + const n = s.length, + m = t.length; + const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0)); + // 状態遷移:先頭行と先頭列 + for (let i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (let j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 状態遷移: 残りの行と列 + for (let i = 1; i <= n; i++) { + for (let j = 1; j <= m; j++) { + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[i][j] = + Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* 編集距離:空間最適化した動的計画法 */ +function editDistanceDPComp(s, t) { + const n = s.length, + m = t.length; + const dp = new Array(m + 1).fill(0); + // 状態遷移:先頭行 + for (let j = 1; j <= m; j++) { + dp[j] = j; + } + // 状態遷移:残りの行 + for (let i = 1; i <= n; i++) { + // 状態遷移:先頭列 + let leftup = dp[0]; // dp[i-1, j-1] を一時保存する + dp[0] = i; + // 状態遷移:残りの列 + for (let j = 1; j <= m; j++) { + const temp = dp[j]; + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[j] = leftup; + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1; + } + leftup = temp; // 次の反復の dp[i-1, j-1] に更新する + } + } + return dp[m]; +} + +const s = 'bag'; +const t = 'pack'; +const n = s.length, + m = t.length; + +// 全探索 +let res = editDistanceDFS(s, t, n, m); +console.log(`${s} を ${t} に変更するには最少で ${res} 回の編集が必要`); + +// メモ化探索 +const mem = Array.from(new Array(n + 1), () => new Array(m + 1).fill(-1)); +res = editDistanceDFSMem(s, t, mem, n, m); +console.log(`${s} を ${t} に変更するには最少で ${res} 回の編集が必要`); + +// 動的計画法 +res = editDistanceDP(s, t); +console.log(`${s} を ${t} に変更するには最少で ${res} 回の編集が必要`); + +// 空間最適化後の動的計画法 +res = editDistanceDPComp(s, t); +console.log(`${s} を ${t} に変更するには最少で ${res} 回の編集が必要`); diff --git a/ja/codes/javascript/chapter_dynamic_programming/knapsack.js b/ja/codes/javascript/chapter_dynamic_programming/knapsack.js new file mode 100644 index 000000000..c6b37b13b --- /dev/null +++ b/ja/codes/javascript/chapter_dynamic_programming/knapsack.js @@ -0,0 +1,113 @@ +/** + * File: knapsack.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 0-1 ナップサック:総当たり探索 */ +function knapsackDFS(wgt, val, i, c) { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if (i === 0 || c === 0) { + return 0; + } + // ナップサック容量を超える場合は、入れない選択しかできない + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + const no = knapsackDFS(wgt, val, i - 1, c); + const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 2つの案のうち価値が大きいほうを返す + return Math.max(no, yes); +} + +/* 0-1 ナップサック:メモ化探索 */ +function knapsackDFSMem(wgt, val, mem, i, c) { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if (i === 0 || c === 0) { + return 0; + } + // 既に記録があればそのまま返す + if (mem[i][c] !== -1) { + return mem[i][c]; + } + // ナップサック容量を超える場合は、入れない選択しかできない + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + const no = knapsackDFSMem(wgt, val, mem, i - 1, c); + const yes = + knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 2 つの案のうち価値が大きい方を記録して返す + mem[i][c] = Math.max(no, yes); + return mem[i][c]; +} + +/* 0-1 ナップサック:動的計画法 */ +function knapsackDP(wgt, val, cap) { + const n = wgt.length; + // dp テーブルを初期化 + const dp = Array(n + 1) + .fill(0) + .map(() => Array(cap + 1).fill(0)); + // 状態遷移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[i][c] = dp[i - 1][c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i - 1][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* 0-1 ナップサック:空間最適化後の動的計画法 */ +function knapsackDPComp(wgt, val, cap) { + const n = wgt.length; + // dp テーブルを初期化 + const dp = Array(cap + 1).fill(0); + // 状態遷移 + for (let i = 1; i <= n; i++) { + // 逆順に走査する + for (let c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [10, 20, 30, 40, 50]; +const val = [50, 120, 150, 210, 240]; +const cap = 50; +const n = wgt.length; + +// 全探索 +let res = knapsackDFS(wgt, val, n, cap); +console.log(`ナップサック容量を超えない最大価値は ${res}`); + +// メモ化探索 +const mem = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => -1) +); +res = knapsackDFSMem(wgt, val, mem, n, cap); +console.log(`ナップサック容量を超えない最大価値は ${res}`); + +// 動的計画法 +res = knapsackDP(wgt, val, cap); +console.log(`ナップサック容量を超えない最大価値は ${res}`); + +// 空間最適化後の動的計画法 +res = knapsackDPComp(wgt, val, cap); +console.log(`ナップサック容量を超えない最大価値は ${res}`); diff --git a/ja/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js b/ja/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js new file mode 100644 index 000000000..a0fde42ee --- /dev/null +++ b/ja/codes/javascript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.js @@ -0,0 +1,49 @@ +/** + * File: min_cost_climbing_stairs_dp.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 階段登りの最小コスト:動的計画法 */ +function minCostClimbingStairsDP(cost) { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + // 部分問題の解を保存するために dp テーブルを初期化 + const dp = new Array(n + 1); + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for (let i = 3; i <= n; i++) { + dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* 階段昇りの最小コスト:空間最適化後の動的計画法 */ +function minCostClimbingStairsDPComp(cost) { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + let a = cost[1], + b = cost[2]; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = Math.min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; +console.log('入力された階段コストのリストは:', cost); + +let res = minCostClimbingStairsDP(cost); +console.log(`階段を上り切る最小コストは:${res}`); + +res = minCostClimbingStairsDPComp(cost); +console.log(`階段を上り切る最小コストは:${res}`); diff --git a/ja/codes/javascript/chapter_dynamic_programming/min_path_sum.js b/ja/codes/javascript/chapter_dynamic_programming/min_path_sum.js new file mode 100644 index 000000000..513e53706 --- /dev/null +++ b/ja/codes/javascript/chapter_dynamic_programming/min_path_sum.js @@ -0,0 +1,121 @@ +/** + * File: min_path_sum.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 最小経路和:全探索 */ +function minPathSumDFS(grid, i, j) { + // 左上のセルなら探索を終了する + if (i === 0 && j === 0) { + return grid[0][0]; + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if (i < 0 || j < 0) { + return Infinity; + } + // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する + const up = minPathSumDFS(grid, i - 1, j); + const left = minPathSumDFS(grid, i, j - 1); + // 左上隅から (i, j) までの最小経路コストを返す + return Math.min(left, up) + grid[i][j]; +} + +/* 最小経路和:メモ化探索 */ +function minPathSumDFSMem(grid, mem, i, j) { + // 左上のセルなら探索を終了する + if (i === 0 && j === 0) { + return grid[0][0]; + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if (i < 0 || j < 0) { + return Infinity; + } + // 既に記録があればそのまま返す + if (mem[i][j] !== -1) { + return mem[i][j]; + } + // 左と上のセルからの最小経路コスト + const up = minPathSumDFSMem(grid, mem, i - 1, j); + const left = minPathSumDFSMem(grid, mem, i, j - 1); + // 左上から (i, j) までの最小経路コストを記録して返す + mem[i][j] = Math.min(left, up) + grid[i][j]; + return mem[i][j]; +} + +/* 最小経路和:動的計画法 */ +function minPathSumDP(grid) { + const n = grid.length, + m = grid[0].length; + // dp テーブルを初期化 + const dp = Array.from({ length: n }, () => + Array.from({ length: m }, () => 0) + ); + dp[0][0] = grid[0][0]; + // 状態遷移:先頭行 + for (let j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 状態遷移:先頭列 + for (let i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 状態遷移: 残りの行と列 + for (let i = 1; i < n; i++) { + for (let j = 1; j < m; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* 最小経路和:空間最適化後の動的計画法 */ +function minPathSumDPComp(grid) { + const n = grid.length, + m = grid[0].length; + // dp テーブルを初期化 + const dp = new Array(m); + // 状態遷移:先頭行 + dp[0] = grid[0][0]; + for (let j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状態遷移:残りの行 + for (let i = 1; i < n; i++) { + // 状態遷移:先頭列 + dp[0] = dp[0] + grid[i][0]; + // 状態遷移:残りの列 + for (let j = 1; j < m; j++) { + dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +const grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], +]; +const n = grid.length, + m = grid[0].length; +// 全探索 +let res = minPathSumDFS(grid, n - 1, m - 1); +console.log(`左上から右下までの最小経路和は ${res}`); + +// メモ化探索 +const mem = Array.from({ length: n }, () => + Array.from({ length: m }, () => -1) +); +res = minPathSumDFSMem(grid, mem, n - 1, m - 1); +console.log(`左上から右下までの最小経路和は ${res}`); + +// 動的計画法 +res = minPathSumDP(grid); +console.log(`左上から右下までの最小経路和は ${res}`); + +// 空間最適化後の動的計画法 +res = minPathSumDPComp(grid); +console.log(`左上から右下までの最小経路和は ${res}`); diff --git a/ja/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js b/ja/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js new file mode 100644 index 000000000..4978145a6 --- /dev/null +++ b/ja/codes/javascript/chapter_dynamic_programming/unbounded_knapsack.js @@ -0,0 +1,63 @@ +/** + * File: unbounded_knapsack.js + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 完全ナップサック問題:動的計画法 */ +function unboundedKnapsackDP(wgt, val, cap) { + const n = wgt.length; + // dp テーブルを初期化 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => 0) + ); + // 状態遷移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[i][c] = dp[i - 1][c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* 完全ナップサック問題:空間最適化後の動的計画法 */ +function unboundedKnapsackDPComp(wgt, val, cap) { + const n = wgt.length; + // dp テーブルを初期化 + const dp = Array.from({ length: cap + 1 }, () => 0); + // 状態遷移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[c] = dp[c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [1, 2, 3]; +const val = [5, 11, 15]; +const cap = 4; + +// 動的計画法 +let res = unboundedKnapsackDP(wgt, val, cap); +console.log(`ナップサック容量を超えない最大価値は ${res}`); + +// 空間最適化後の動的計画法 +res = unboundedKnapsackDPComp(wgt, val, cap); +console.log(`ナップサック容量を超えない最大価値は ${res}`); diff --git a/ja/codes/javascript/chapter_graph/graph_adjacency_list.js b/ja/codes/javascript/chapter_graph/graph_adjacency_list.js new file mode 100644 index 000000000..c11f7b067 --- /dev/null +++ b/ja/codes/javascript/chapter_graph/graph_adjacency_list.js @@ -0,0 +1,142 @@ +/** + * File: graph_adjacency_list.js + * Created Time: 2023-02-09 + * Author: Justin (xiefahit@gmail.com) + */ + +const { Vertex } = require('../modules/Vertex'); + +/* 隣接リストに基づく無向グラフクラス */ +class GraphAdjList { + // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 + adjList; + + /* コンストラクタ */ + constructor(edges) { + this.adjList = new Map(); + // すべての頂点と辺を追加 + for (const edge of edges) { + this.addVertex(edge[0]); + this.addVertex(edge[1]); + this.addEdge(edge[0], edge[1]); + } + } + + /* 頂点数を取得 */ + size() { + return this.adjList.size; + } + + /* 辺を追加 */ + addEdge(vet1, vet2) { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 + ) { + throw new Error('Illegal Argument Exception'); + } + // 辺 vet1 - vet2 を追加 + this.adjList.get(vet1).push(vet2); + this.adjList.get(vet2).push(vet1); + } + + /* 辺を削除 */ + removeEdge(vet1, vet2) { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 || + this.adjList.get(vet1).indexOf(vet2) === -1 + ) { + throw new Error('Illegal Argument Exception'); + } + // 辺 vet1 - vet2 を削除 + this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); + this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); + } + + /* 頂点を追加 */ + addVertex(vet) { + if (this.adjList.has(vet)) return; + // 隣接リストに新しいリストを追加 + this.adjList.set(vet, []); + } + + /* 頂点を削除 */ + removeVertex(vet) { + if (!this.adjList.has(vet)) { + throw new Error('Illegal Argument Exception'); + } + // 隣接リストから頂点 vet に対応するリストを削除 + this.adjList.delete(vet); + // 他の頂点のリストを走査し、vet を含むすべての辺を削除 + for (const set of this.adjList.values()) { + const index = set.indexOf(vet); + if (index > -1) { + set.splice(index, 1); + } + } + } + + /* 隣接リストを出力 */ + print() { + console.log('隣接リスト ='); + for (const [key, value] of this.adjList) { + const tmp = []; + for (const vertex of value) { + tmp.push(vertex.val); + } + console.log(key.val + ': ' + tmp.join()); + } + } +} + +if (require.main === module) { + /* Driver Code */ + /* 無向グラフを初期化 */ + const v0 = new Vertex(1), + v1 = new Vertex(3), + v2 = new Vertex(2), + v3 = new Vertex(5), + v4 = new Vertex(4); + const edges = [ + [v0, v1], + [v1, v2], + [v2, v3], + [v0, v3], + [v2, v4], + [v3, v4], + ]; + const graph = new GraphAdjList(edges); + console.log('\n初期化後のグラフは'); + graph.print(); + + /* 辺を追加 */ + // 頂点 1, 2 は v0, v2 + graph.addEdge(v0, v2); + console.log('\n辺 1-2 を追加した後のグラフは'); + graph.print(); + + /* 辺を削除 */ + // 頂点 1, 3 は v0, v1 + graph.removeEdge(v0, v1); + console.log('\n辺 1-3 を削除した後のグラフは'); + graph.print(); + + /* 頂点を追加 */ + const v5 = new Vertex(6); + graph.addVertex(v5); + console.log('\n頂点 6 を追加した後のグラフは'); + graph.print(); + + /* 頂点を削除 */ + // 頂点 3 は v1 + graph.removeVertex(v1); + console.log('\n頂点 3 を削除した後のグラフは'); + graph.print(); +} + +module.exports = { + GraphAdjList, +}; diff --git a/ja/codes/javascript/chapter_graph/graph_adjacency_matrix.js b/ja/codes/javascript/chapter_graph/graph_adjacency_matrix.js new file mode 100644 index 000000000..766f22f5a --- /dev/null +++ b/ja/codes/javascript/chapter_graph/graph_adjacency_matrix.js @@ -0,0 +1,132 @@ +/** + * File: graph_adjacency_matrix.js + * Created Time: 2023-02-09 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 隣接行列に基づく無向グラフクラス */ +class GraphAdjMat { + vertices; // 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す + adjMat; // 隣接行列。行・列のインデックスは「頂点インデックス」に対応 + + /* コンストラクタ */ + constructor(vertices, edges) { + this.vertices = []; + this.adjMat = []; + // 頂点を追加 + for (const val of vertices) { + this.addVertex(val); + } + // 辺を追加 + // 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する + for (const e of edges) { + this.addEdge(e[0], e[1]); + } + } + + /* 頂点数を取得 */ + size() { + return this.vertices.length; + } + + /* 頂点を追加 */ + addVertex(val) { + const n = this.size(); + // 頂点リストに新しい頂点の値を追加 + this.vertices.push(val); + // 隣接行列に 1 行追加 + const newRow = []; + for (let j = 0; j < n; j++) { + newRow.push(0); + } + this.adjMat.push(newRow); + // 隣接行列に 1 列追加 + for (const row of this.adjMat) { + row.push(0); + } + } + + /* 頂点を削除 */ + removeVertex(index) { + if (index >= this.size()) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // 頂点リストから index の頂点を削除する + this.vertices.splice(index, 1); + + // 隣接行列で index 行を削除する + this.adjMat.splice(index, 1); + // 隣接行列で index 列を削除する + for (const row of this.adjMat) { + row.splice(index, 1); + } + } + + /* 辺を追加 */ + // 引数 i, j は vertices の要素インデックスに対応する + addEdge(i, j) { + // インデックスの範囲外と等値の処理 + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // 無向グラフでは、隣接行列は主対角線に関して対称であり、(i, j) === (j, i) を満たす + this.adjMat[i][j] = 1; + this.adjMat[j][i] = 1; + } + + /* 辺を削除 */ + // 引数 i, j は vertices の要素インデックスに対応する + removeEdge(i, j) { + // インデックスの範囲外と等値の処理 + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + this.adjMat[i][j] = 0; + this.adjMat[j][i] = 0; + } + + /* 隣接行列を出力 */ + print() { + console.log('頂点リスト = ', this.vertices); + console.log('隣接行列 =', this.adjMat); + } +} + +/* Driver Code */ +/* 無向グラフを初期化 */ +// edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意 +const vertices = [1, 3, 2, 5, 4]; +const edges = [ + [0, 1], + [1, 2], + [2, 3], + [0, 3], + [2, 4], + [3, 4], +]; +const graph = new GraphAdjMat(vertices, edges); +console.log('\n初期化後のグラフは'); +graph.print(); + +/* 辺を追加 */ +// 頂点 1, 2 のインデックスはそれぞれ 0, 2 +graph.addEdge(0, 2); +console.log('\n辺 1-2 を追加した後のグラフは'); +graph.print(); + +/* 辺を削除 */ +// 頂点 1, 3 のインデックスはそれぞれ 0, 1 +graph.removeEdge(0, 1); +console.log('\n辺 1-3 を削除した後のグラフは'); +graph.print(); + +/* 頂点を追加 */ +graph.addVertex(6); +console.log('\n頂点 6 を追加した後のグラフは'); +graph.print(); + +/* 頂点を削除 */ +// 頂点 3 のインデックスは 1 +graph.removeVertex(1); +console.log('\n頂点 3 を削除した後のグラフは'); +graph.print(); diff --git a/ja/codes/javascript/chapter_graph/graph_bfs.js b/ja/codes/javascript/chapter_graph/graph_bfs.js new file mode 100644 index 000000000..7bbab7947 --- /dev/null +++ b/ja/codes/javascript/chapter_graph/graph_bfs.js @@ -0,0 +1,61 @@ +/** + * File: graph_bfs.js + * Created Time: 2023-02-21 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +const { GraphAdjList } = require('./graph_adjacency_list'); +const { Vertex } = require('../modules/Vertex'); + +/* 幅優先探索 */ +// グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする +function graphBFS(graph, startVet) { + // 頂点の走査順序 + const res = []; + // 訪問済み頂点を記録するためのハッシュ集合 + const visited = new Set(); + visited.add(startVet); + // BFS の実装にキューを用いる + const que = [startVet]; + // 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す + while (que.length) { + const vet = que.shift(); // 先頭の頂点をデキュー + res.push(vet); // 訪問した頂点を記録 + // この頂点のすべての隣接頂点を走査 + for (const adjVet of graph.adjList.get(vet) ?? []) { + if (visited.has(adjVet)) { + continue; // 訪問済みの頂点をスキップ + } + que.push(adjVet); // 未訪問の頂点のみをキューに追加 + visited.add(adjVet); // この頂点を訪問済みにする + } + } + // 頂点の走査順を返す + return res; +} + +/* Driver Code */ +/* 無向グラフを初期化 */ +const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); +const edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[1], v[4]], + [v[2], v[5]], + [v[3], v[4]], + [v[3], v[6]], + [v[4], v[5]], + [v[4], v[7]], + [v[5], v[8]], + [v[6], v[7]], + [v[7], v[8]], +]; +const graph = new GraphAdjList(edges); +console.log('\n初期化後のグラフは'); +graph.print(); + +/* 幅優先探索 */ +const res = graphBFS(graph, v[0]); +console.log('\n幅優先探索(BFS)の頂点列は'); +console.log(Vertex.vetsToVals(res)); diff --git a/ja/codes/javascript/chapter_graph/graph_dfs.js b/ja/codes/javascript/chapter_graph/graph_dfs.js new file mode 100644 index 000000000..c4518e13e --- /dev/null +++ b/ja/codes/javascript/chapter_graph/graph_dfs.js @@ -0,0 +1,54 @@ +/** + * File: graph_dfs.js + * Created Time: 2023-02-21 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +const { Vertex } = require('../modules/Vertex'); +const { GraphAdjList } = require('./graph_adjacency_list'); + +/* 深さ優先探索 */ +// グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする +function dfs(graph, visited, res, vet) { + res.push(vet); // 訪問した頂点を記録 + visited.add(vet); // この頂点を訪問済みにする + // この頂点のすべての隣接頂点を走査 + for (const adjVet of graph.adjList.get(vet)) { + if (visited.has(adjVet)) { + continue; // 訪問済みの頂点をスキップ + } + // 隣接頂点を再帰的に訪問 + dfs(graph, visited, res, adjVet); + } +} + +/* 深さ優先探索 */ +// グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする +function graphDFS(graph, startVet) { + // 頂点の走査順序 + const res = []; + // 訪問済み頂点を記録するためのハッシュ集合 + const visited = new Set(); + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +/* 無向グラフを初期化 */ +const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); +const edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], +]; +const graph = new GraphAdjList(edges); +console.log('\n初期化後のグラフは'); +graph.print(); + +/* 深さ優先探索 */ +const res = graphDFS(graph, v[0]); +console.log('\n深さ優先探索(DFS)の頂点列は'); +console.log(Vertex.vetsToVals(res)); diff --git a/ja/codes/javascript/chapter_greedy/coin_change_greedy.js b/ja/codes/javascript/chapter_greedy/coin_change_greedy.js new file mode 100644 index 000000000..c782ff310 --- /dev/null +++ b/ja/codes/javascript/chapter_greedy/coin_change_greedy.js @@ -0,0 +1,48 @@ +/** + * File: coin_change_greedy.js + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* コイン交換:貪欲法 */ +function coinChangeGreedy(coins, amt) { + // coins 配列はソート済みと仮定する + let i = coins.length - 1; + let count = 0; + // 残額がなくなるまで貪欲選択を繰り返す + while (amt > 0) { + // 残額以下で最も近い硬貨を見つける + while (i > 0 && coins[i] > amt) { + i--; + } + // coins[i] を選択する + amt -= coins[i]; + count++; + } + // 実行可能な解が見つからなければ -1 を返す + return amt === 0 ? count : -1; +} + +/* Driver Code */ +// 貪欲法:大域最適解を保証できる +let coins = [1, 5, 10, 20, 50, 100]; +let amt = 186; +let res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`${amt} を作るのに必要な最小硬貨枚数は ${res}`); + +// 貪欲法:大域最適解を保証できない +coins = [1, 20, 50]; +amt = 60; +res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`${amt} を作るのに必要な最小硬貨枚数は ${res}`); +console.log('実際に必要な最小枚数は 3、つまり 20 + 20 + 20'); + +// 貪欲法:大域最適解を保証できない +coins = [1, 49, 50]; +amt = 98; +res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`${amt} を作るのに必要な最小硬貨枚数は ${res}`); +console.log('実際に必要な最小枚数は 2、つまり 49 + 49'); diff --git a/ja/codes/javascript/chapter_greedy/fractional_knapsack.js b/ja/codes/javascript/chapter_greedy/fractional_knapsack.js new file mode 100644 index 000000000..b99cec191 --- /dev/null +++ b/ja/codes/javascript/chapter_greedy/fractional_knapsack.js @@ -0,0 +1,46 @@ +/** + * File: fractional_knapsack.js + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 品物 */ +class Item { + constructor(w, v) { + this.w = w; // 品物の重さ + this.v = v; // 品物の価値 + } +} + +/* 分数ナップサック:貪欲法 */ +function fractionalKnapsack(wgt, val, cap) { + // 重さと価値の 2 属性を持つ品物リストを作成 + const items = wgt.map((w, i) => new Item(w, val[i])); + // 単位価値 item.v / item.w の高い順にソートする + items.sort((a, b) => b.v / b.w - a.v / a.w); + // 貪欲選択を繰り返す + let res = 0; + for (const item of items) { + if (item.w <= cap) { + // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる + res += item.v; + cap -= item.w; + } else { + // 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる + res += (item.v / item.w) * cap; + // 残り容量がないため、ループを抜ける + break; + } + } + return res; +} + +/* Driver Code */ +const wgt = [10, 20, 30, 40, 50]; +const val = [50, 120, 150, 210, 240]; +const cap = 50; +const n = wgt.length; + +// 貪欲法 +const res = fractionalKnapsack(wgt, val, cap); +console.log(`ナップサック容量を超えない最大価値は ${res}`); diff --git a/ja/codes/javascript/chapter_greedy/max_capacity.js b/ja/codes/javascript/chapter_greedy/max_capacity.js new file mode 100644 index 000000000..3ab9d5b2e --- /dev/null +++ b/ja/codes/javascript/chapter_greedy/max_capacity.js @@ -0,0 +1,34 @@ +/** + * File: max_capacity.js + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 最大容量:貪欲法 */ +function maxCapacity(ht) { + // i, j を初期化し、それぞれ配列の両端に置く + let i = 0, + j = ht.length - 1; + // 初期の最大容量は 0 + let res = 0; + // 2 枚の板が出会うまで貪欲選択を繰り返す + while (i < j) { + // 最大容量を更新する + const cap = Math.min(ht[i], ht[j]) * (j - i); + res = Math.max(res, cap); + // 短い方を内側へ動かす + if (ht[i] < ht[j]) { + i += 1; + } else { + j -= 1; + } + } + return res; +} + +/* Driver Code */ +const ht = [3, 8, 5, 2, 7, 7, 3, 4]; + +// 貪欲法 +const res = maxCapacity(ht); +console.log(`最大容量は ${res}`); diff --git a/ja/codes/javascript/chapter_greedy/max_product_cutting.js b/ja/codes/javascript/chapter_greedy/max_product_cutting.js new file mode 100644 index 000000000..a00f3c39b --- /dev/null +++ b/ja/codes/javascript/chapter_greedy/max_product_cutting.js @@ -0,0 +1,33 @@ +/** + * File: max_product_cutting.js + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 最大切断積:貪欲法 */ +function maxProductCutting(n) { + // n <= 3 のときは、必ず 1 を切り出す + if (n <= 3) { + return 1 * (n - 1); + } + // 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする + let a = Math.floor(n / 3); + let b = n % 3; + if (b === 1) { + // 余りが 1 のときは、1 * 3 を 2 * 2 に変える + return Math.pow(3, a - 1) * 2 * 2; + } + if (b === 2) { + // 余りが 2 のときは、そのままにする + return Math.pow(3, a) * 2; + } + // 余りが 0 のときは、そのままにする + return Math.pow(3, a); +} + +/* Driver Code */ +let n = 58; + +// 貪欲法 +let res = maxProductCutting(n); +console.log(`最大分割積は ${res}`); diff --git a/ja/codes/javascript/chapter_hashing/array_hash_map.js b/ja/codes/javascript/chapter_hashing/array_hash_map.js new file mode 100644 index 000000000..ba78145a7 --- /dev/null +++ b/ja/codes/javascript/chapter_hashing/array_hash_map.js @@ -0,0 +1,128 @@ +/** + * File: array_hash_map.js + * Created Time: 2022-12-26 + * Author: Justin (xiefahit@gmail.com) + */ + +/* キーと値の組 Number -> String */ +class Pair { + constructor(key, val) { + this.key = key; + this.val = val; + } +} + +/* 配列ベースのハッシュテーブル */ +class ArrayHashMap { + #buckets; + constructor() { + // 100 個のバケットを含む配列を初期化 + this.#buckets = new Array(100).fill(null); + } + + /* ハッシュ関数 */ + #hashFunc(key) { + return key % 100; + } + + /* 検索操作 */ + get(key) { + let index = this.#hashFunc(key); + let pair = this.#buckets[index]; + if (pair === null) return null; + return pair.val; + } + + /* 追加操作 */ + set(key, val) { + let index = this.#hashFunc(key); + this.#buckets[index] = new Pair(key, val); + } + + /* 削除操作 */ + delete(key) { + let index = this.#hashFunc(key); + // null に設定し、削除を表す + this.#buckets[index] = null; + } + + /* すべてのキーと値のペアを取得 */ + entries() { + let arr = []; + for (let i = 0; i < this.#buckets.length; i++) { + if (this.#buckets[i]) { + arr.push(this.#buckets[i]); + } + } + return arr; + } + + /* すべてのキーを取得 */ + keys() { + let arr = []; + for (let i = 0; i < this.#buckets.length; i++) { + if (this.#buckets[i]) { + arr.push(this.#buckets[i].key); + } + } + return arr; + } + + /* すべての値を取得 */ + values() { + let arr = []; + for (let i = 0; i < this.#buckets.length; i++) { + if (this.#buckets[i]) { + arr.push(this.#buckets[i].val); + } + } + return arr; + } + + /* ハッシュテーブルを出力 */ + print() { + let pairSet = this.entries(); + for (const pair of pairSet) { + console.info(`${pair.key} -> ${pair.val}`); + } + } +} + +/* Driver Code */ +/* ハッシュテーブルを初期化 */ +const map = new ArrayHashMap(); +/* 追加操作 */ +// ハッシュテーブルにキーと値のペア (key, value) を追加 +map.set(12836, 'シャオハー'); +map.set(15937, 'シャオルオ'); +map.set(16750, 'シャオスワン'); +map.set(13276, 'シャオファー'); +map.set(10583, 'シャオヤー'); +console.info('\n追加完了後、ハッシュ表は\nKey -> Value'); +map.print(); + +/* 検索操作 */ +// キー key をハッシュテーブルに渡し、値 value を取得 +let name = map.get(15937); +console.info('\n学籍番号 15937 を入力すると、氏名 ' + name); + +/* 削除操作 */ +// ハッシュテーブルからキーと値のペア (key, value) を削除 +map.delete(10583); +console.info('\n10583 を削除した後、ハッシュ表は\nKey -> Value'); +map.print(); + +/* ハッシュテーブルを走査 */ +console.info('\nキーと値のペア Key->Value を走査'); +for (const pair of map.entries()) { + if (!pair) continue; + console.info(pair.key + ' -> ' + pair.val); +} +console.info('\nキー Key を個別に走査'); +for (const key of map.keys()) { + console.info(key); +} +console.info('\n値 Value を個別に走査'); +for (const val of map.values()) { + console.info(val); +} diff --git a/ja/codes/javascript/chapter_hashing/hash_map.js b/ja/codes/javascript/chapter_hashing/hash_map.js new file mode 100644 index 000000000..3e02f49bc --- /dev/null +++ b/ja/codes/javascript/chapter_hashing/hash_map.js @@ -0,0 +1,44 @@ +/** + * File: hash_map.js + * Created Time: 2022-12-26 + * Author: Justin (xiefahit@gmail.com) + */ + +/* Driver Code */ +/* ハッシュテーブルを初期化 */ +const map = new Map(); + +/* 追加操作 */ +// ハッシュテーブルにキーと値のペア (key, value) を追加 +map.set(12836, 'シャオハー'); +map.set(15937, 'シャオルオ'); +map.set(16750, 'シャオスワン'); +map.set(13276, 'シャオファー'); +map.set(10583, 'シャオヤー'); +console.info('\n追加完了後、ハッシュ表は\nKey -> Value'); +console.info(map); + +/* 検索操作 */ +// キー key をハッシュテーブルに渡し、値 value を取得 +let name = map.get(15937); +console.info('\n学籍番号 15937 を入力すると、氏名 ' + name); + +/* 削除操作 */ +// ハッシュテーブルからキーと値のペア (key, value) を削除 +map.delete(10583); +console.info('\n10583 を削除した後、ハッシュ表は\nKey -> Value'); +console.info(map); + +/* ハッシュテーブルを走査 */ +console.info('\nキーと値のペア Key->Value を走査'); +for (const [k, v] of map.entries()) { + console.info(k + ' -> ' + v); +} +console.info('\nキー Key を個別に走査'); +for (const k of map.keys()) { + console.info(k); +} +console.info('\n値 Value を個別に走査'); +for (const v of map.values()) { + console.info(v); +} diff --git a/ja/codes/javascript/chapter_hashing/hash_map_chaining.js b/ja/codes/javascript/chapter_hashing/hash_map_chaining.js new file mode 100644 index 000000000..689883f0a --- /dev/null +++ b/ja/codes/javascript/chapter_hashing/hash_map_chaining.js @@ -0,0 +1,142 @@ +/** + * File: hash_map_chaining.js + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* キーと値の組 Number -> String */ +class Pair { + constructor(key, val) { + this.key = key; + this.val = val; + } +} + +/* チェイン法ハッシュテーブル */ +class HashMapChaining { + #size; // キーと値のペア数 + #capacity; // ハッシュテーブル容量 + #loadThres; // リサイズを発動する負荷率のしきい値 + #extendRatio; // 拡張倍率 + #buckets; // バケット配列 + + /* コンストラクタ */ + constructor() { + this.#size = 0; + this.#capacity = 4; + this.#loadThres = 2.0 / 3.0; + this.#extendRatio = 2; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + } + + /* ハッシュ関数 */ + #hashFunc(key) { + return key % this.#capacity; + } + + /* 負荷率 */ + #loadFactor() { + return this.#size / this.#capacity; + } + + /* 検索操作 */ + get(key) { + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // バケットを走査し、key が見つかれば対応する val を返す + for (const pair of bucket) { + if (pair.key === key) { + return pair.val; + } + } + // key が見つからない場合は null を返す + return null; + } + + /* 追加操作 */ + put(key, val) { + // 負荷率がしきい値を超えたら、リサイズを実行 + if (this.#loadFactor() > this.#loadThres) { + this.#extend(); + } + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // バケットを走査し、指定した key が見つかれば対応する val を更新して返す + for (const pair of bucket) { + if (pair.key === key) { + pair.val = val; + return; + } + } + // その key が存在しなければ、キーと値のペアを末尾に追加 + const pair = new Pair(key, val); + bucket.push(pair); + this.#size++; + } + + /* 削除操作 */ + remove(key) { + const index = this.#hashFunc(key); + let bucket = this.#buckets[index]; + // バケットを走査してキーと値のペアを削除 + for (let i = 0; i < bucket.length; i++) { + if (bucket[i].key === key) { + bucket.splice(i, 1); + this.#size--; + break; + } + } + } + + /* ハッシュテーブルを拡張 */ + #extend() { + // 元のハッシュテーブルを一時保存 + const bucketsTmp = this.#buckets; + // リサイズ後の新しいハッシュテーブルを初期化 + this.#capacity *= this.#extendRatio; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + this.#size = 0; + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す + for (const bucket of bucketsTmp) { + for (const pair of bucket) { + this.put(pair.key, pair.val); + } + } + } + + /* ハッシュテーブルを出力 */ + print() { + for (const bucket of this.#buckets) { + let res = []; + for (const pair of bucket) { + res.push(pair.key + ' -> ' + pair.val); + } + console.log(res); + } + } +} + +/* Driver Code */ +/* ハッシュテーブルを初期化 */ +const map = new HashMapChaining(); + +/* 追加操作 */ +// ハッシュテーブルにキーと値のペア (key, value) を追加 +map.put(12836, 'シャオハー'); +map.put(15937, 'シャオルオ'); +map.put(16750, 'シャオスワン'); +map.put(13276, 'シャオファー'); +map.put(10583, 'シャオヤー'); +console.log('\n追加完了後、ハッシュ表は\nKey -> Value'); +map.print(); + +/* 検索操作 */ +// キー key をハッシュテーブルに渡し、値 value を取得 +const name = map.get(13276); +console.log('\n学籍番号 13276 を入力すると、名前 ' + name); + +/* 削除操作 */ +// ハッシュテーブルからキーと値のペア (key, value) を削除 +map.remove(12836); +console.log('\n12836 を削除した後、ハッシュテーブルは\nKey -> Value'); +map.print(); diff --git a/ja/codes/javascript/chapter_hashing/hash_map_open_addressing.js b/ja/codes/javascript/chapter_hashing/hash_map_open_addressing.js new file mode 100644 index 000000000..6e6a2f0d8 --- /dev/null +++ b/ja/codes/javascript/chapter_hashing/hash_map_open_addressing.js @@ -0,0 +1,177 @@ +/** + * File: hashMapOpenAddressing.js + * Created Time: 2023-06-13 + * Author: yuan0221 (yl1452491917@gmail.com), krahets (krahets@163.com) + */ + +/* キーと値の組 Number -> String */ +class Pair { + constructor(key, val) { + this.key = key; + this.val = val; + } +} + +/* オープンアドレス法ハッシュテーブル */ +class HashMapOpenAddressing { + #size; // キーと値のペア数 + #capacity; // ハッシュテーブル容量 + #loadThres; // リサイズを発動する負荷率のしきい値 + #extendRatio; // 拡張倍率 + #buckets; // バケット配列 + #TOMBSTONE; // 削除済みマーク + + /* コンストラクタ */ + constructor() { + this.#size = 0; // キーと値のペア数 + this.#capacity = 4; // ハッシュテーブル容量 + this.#loadThres = 2.0 / 3.0; // リサイズを発動する負荷率のしきい値 + this.#extendRatio = 2; // 拡張倍率 + this.#buckets = Array(this.#capacity).fill(null); // バケット配列 + this.#TOMBSTONE = new Pair(-1, '-1'); // 削除済みマーク + } + + /* ハッシュ関数 */ + #hashFunc(key) { + return key % this.#capacity; + } + + /* 負荷率 */ + #loadFactor() { + return this.#size / this.#capacity; + } + + /* key に対応するバケットインデックスを探す */ + #findBucket(key) { + let index = this.#hashFunc(key); + let firstTombstone = -1; + // 線形プロービングを行い、空バケットに達したら終了 + while (this.#buckets[index] !== null) { + // key が見つかったら、対応するバケットのインデックスを返す + if (this.#buckets[index].key === key) { + // 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 + if (firstTombstone !== -1) { + this.#buckets[firstTombstone] = this.#buckets[index]; + this.#buckets[index] = this.#TOMBSTONE; + return firstTombstone; // 移動後のバケットインデックスを返す + } + return index; // バケットのインデックスを返す + } + // 最初に見つかった削除マークを記録 + if ( + firstTombstone === -1 && + this.#buckets[index] === this.#TOMBSTONE + ) { + firstTombstone = index; + } + // バケットのインデックスを計算し、末尾を越えたら先頭に戻る + index = (index + 1) % this.#capacity; + } + // key が存在しない場合は追加位置のインデックスを返す + return firstTombstone === -1 ? index : firstTombstone; + } + + /* 検索操作 */ + get(key) { + // key に対応するバケットインデックスを探す + const index = this.#findBucket(key); + // キーと値の組が見つかったら、対応する val を返す + if ( + this.#buckets[index] !== null && + this.#buckets[index] !== this.#TOMBSTONE + ) { + return this.#buckets[index].val; + } + // キーと値の組が存在しなければ null を返す + return null; + } + + /* 追加操作 */ + put(key, val) { + // 負荷率がしきい値を超えたら、リサイズを実行 + if (this.#loadFactor() > this.#loadThres) { + this.#extend(); + } + // key に対応するバケットインデックスを探す + const index = this.#findBucket(key); + // キーと値の組が見つかったら、val を上書きして返す + if ( + this.#buckets[index] !== null && + this.#buckets[index] !== this.#TOMBSTONE + ) { + this.#buckets[index].val = val; + return; + } + // キーと値の組が存在しない場合は、その組を追加する + this.#buckets[index] = new Pair(key, val); + this.#size++; + } + + /* 削除操作 */ + remove(key) { + // key に対応するバケットインデックスを探す + const index = this.#findBucket(key); + // キーと値の組が見つかったら、削除マーカーで上書きする + if ( + this.#buckets[index] !== null && + this.#buckets[index] !== this.#TOMBSTONE + ) { + this.#buckets[index] = this.#TOMBSTONE; + this.#size--; + } + } + + /* ハッシュテーブルを拡張 */ + #extend() { + // 元のハッシュテーブルを一時保存 + const bucketsTmp = this.#buckets; + // リサイズ後の新しいハッシュテーブルを初期化 + this.#capacity *= this.#extendRatio; + this.#buckets = Array(this.#capacity).fill(null); + this.#size = 0; + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す + for (const pair of bucketsTmp) { + if (pair !== null && pair !== this.#TOMBSTONE) { + this.put(pair.key, pair.val); + } + } + } + + /* ハッシュテーブルを出力 */ + print() { + for (const pair of this.#buckets) { + if (pair === null) { + console.log('null'); + } else if (pair === this.#TOMBSTONE) { + console.log('TOMBSTONE'); + } else { + console.log(pair.key + ' -> ' + pair.val); + } + } + } +} + +/* Driver Code */ +// ハッシュテーブルを初期化 +const hashmap = new HashMapOpenAddressing(); + +// 追加操作 +// ハッシュテーブルにキーと値の組 (key, val) を追加する +hashmap.put(12836, 'シャオハー'); +hashmap.put(15937, 'シャオルオ'); +hashmap.put(16750, 'シャオスワン'); +hashmap.put(13276, 'シャオファー'); +hashmap.put(10583, 'シャオヤー'); +console.log('\n追加完了後、ハッシュ表は\nKey -> Value'); +hashmap.print(); + +// 検索操作 +// ハッシュテーブルにキー key を入力し、値 val を得る +const name = hashmap.get(13276); +console.log('\n学籍番号 13276 を入力すると、名前 ' + name); + +// 削除操作 +// ハッシュテーブルからキーと値の組 (key, val) を削除する +hashmap.remove(16750); +console.log('\n16750 を削除した後、ハッシュテーブルは\nKey -> Value'); +hashmap.print(); diff --git a/ja/codes/javascript/chapter_hashing/simple_hash.js b/ja/codes/javascript/chapter_hashing/simple_hash.js new file mode 100644 index 000000000..789244020 --- /dev/null +++ b/ja/codes/javascript/chapter_hashing/simple_hash.js @@ -0,0 +1,60 @@ +/** + * File: simple_hash.js + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 加算ハッシュ */ +function addHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* 乗算ハッシュ */ +function mulHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (31 * hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* XOR ハッシュ */ +function xorHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash ^= c.charCodeAt(0); + } + return hash % MODULUS; +} + +/* 回転ハッシュ */ +function rotHash(key) { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* Driver Code */ +const key = 'Hello アルゴリズム'; + +let hash = addHash(key); +console.log('加算ハッシュ値は ' + hash); + +hash = mulHash(key); +console.log('乗算ハッシュ値は ' + hash); + +hash = xorHash(key); +console.log('XORハッシュ値は ' + hash); + +hash = rotHash(key); +console.log('回転ハッシュ値は ' + hash); diff --git a/ja/codes/javascript/chapter_heap/my_heap.js b/ja/codes/javascript/chapter_heap/my_heap.js new file mode 100644 index 000000000..ea0ae3b2a --- /dev/null +++ b/ja/codes/javascript/chapter_heap/my_heap.js @@ -0,0 +1,158 @@ +/** + * File: my_heap.js + * Created Time: 2023-02-06 + * Author: what-is-me (whatisme@outlook.jp) + */ + +const { printHeap } = require('../modules/PrintUtil'); + +/* 最大ヒープクラス */ +class MaxHeap { + #maxHeap; + + /* コンストラクタ。空のヒープを作成するか、入力リストからヒープを構築する */ + constructor(nums) { + // リスト要素をそのままヒープに追加 + this.#maxHeap = nums === undefined ? [] : [...nums]; + // 葉ノード以外のすべてのノードをヒープ化 + for (let i = this.#parent(this.size() - 1); i >= 0; i--) { + this.#siftDown(i); + } + } + + /* 左子ノードのインデックスを取得 */ + #left(i) { + return 2 * i + 1; + } + + /* 右子ノードのインデックスを取得 */ + #right(i) { + return 2 * i + 2; + } + + /* 親ノードのインデックスを取得 */ + #parent(i) { + return Math.floor((i - 1) / 2); // 切り捨て除算 + } + + /* 要素を交換 */ + #swap(i, j) { + const tmp = this.#maxHeap[i]; + this.#maxHeap[i] = this.#maxHeap[j]; + this.#maxHeap[j] = tmp; + } + + /* ヒープのサイズを取得 */ + size() { + return this.#maxHeap.length; + } + + /* ヒープが空かどうかを判定 */ + isEmpty() { + return this.size() === 0; + } + + /* ヒープ先頭要素にアクセス */ + peek() { + return this.#maxHeap[0]; + } + + /* 要素をヒープに追加 */ + push(val) { + // ノードを追加 + this.#maxHeap.push(val); + // 下から上へヒープ化 + this.#siftUp(this.size() - 1); + } + + /* ノード i から始めて、下から上へヒープ化 */ + #siftUp(i) { + while (true) { + // ノード i の親ノードを取得 + const p = this.#parent(i); + // 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 + if (p < 0 || this.#maxHeap[i] <= this.#maxHeap[p]) break; + // 2 つのノードを交換 + this.#swap(i, p); + // ループで下から上へヒープ化 + i = p; + } + } + + /* 要素をヒープから取り出す */ + pop() { + // 空判定の処理 + if (this.isEmpty()) throw new Error('ヒープが空です'); + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + this.#swap(0, this.size() - 1); + // ノードを削除 + const val = this.#maxHeap.pop(); + // 上から下へヒープ化 + this.#siftDown(0); + // ヒープ先頭要素を返す + return val; + } + + /* ノード i から始めて、上から下へヒープ化 */ + #siftDown(i) { + while (true) { + // ノード i, l, r のうち値が最大のノードを ma とする + const l = this.#left(i), + r = this.#right(i); + let ma = i; + if (l < this.size() && this.#maxHeap[l] > this.#maxHeap[ma]) ma = l; + if (r < this.size() && this.#maxHeap[r] > this.#maxHeap[ma]) ma = r; + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + if (ma === i) break; + // 2 つのノードを交換 + this.#swap(i, ma); + // ループで上から下へヒープ化 + i = ma; + } + } + + /* ヒープ(二分木)を出力 */ + print() { + printHeap(this.#maxHeap); + } + + /* ヒープから要素を取り出す */ + getMaxHeap() { + return this.#maxHeap; + } +} + +/* Driver Code */ +if (require.main === module) { + /* 最大ヒープを初期化 */ + const maxHeap = new MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + console.log('\nリストを入力してヒープを構築した後'); + maxHeap.print(); + + /* ヒープ頂点の要素を取得 */ + let peek = maxHeap.peek(); + console.log(`\nヒープの先頭要素は ${peek}`); + + /* 要素をヒープに追加 */ + let val = 7; + maxHeap.push(val); + console.log(`\n要素 ${val} をヒープに追加した後`); + maxHeap.print(); + + /* ヒープ頂点の要素を取り出す */ + peek = maxHeap.pop(); + console.log(`\nヒープ先頭要素 ${peek} を取り出した後`); + maxHeap.print(); + + /* ヒープのサイズを取得 */ + let size = maxHeap.size(); + console.log(`\nヒープ要素数は ${size}`); + + /* ヒープが空かどうかを判定 */ + let isEmpty = maxHeap.isEmpty(); + console.log(`\nヒープが空かどうかは ${isEmpty}`); +} + +module.exports = { + MaxHeap, +}; diff --git a/ja/codes/javascript/chapter_heap/top_k.js b/ja/codes/javascript/chapter_heap/top_k.js new file mode 100644 index 000000000..d02cbdad4 --- /dev/null +++ b/ja/codes/javascript/chapter_heap/top_k.js @@ -0,0 +1,58 @@ +/** + * File: top_k.js + * Created Time: 2023-08-13 + * Author: Justin (xiefahit@gmail.com) + */ + +const { MaxHeap } = require('./my_heap'); + +/* 要素をヒープに追加 */ +function pushMinHeap(maxHeap, val) { + // 要素を反転する + maxHeap.push(-val); +} + +/* 要素をヒープから取り出す */ +function popMinHeap(maxHeap) { + // 要素を反転する + return -maxHeap.pop(); +} + +/* ヒープ先頭要素にアクセス */ +function peekMinHeap(maxHeap) { + // 要素を反転する + return -maxHeap.peek(); +} + +/* ヒープから要素を取り出す */ +function getMinHeap(maxHeap) { + // 要素を反転する + return maxHeap.getMaxHeap().map((num) => -num); +} + +/* ヒープに基づいて配列中の最大の k 個の要素を探す */ +function topKHeap(nums, k) { + // 最小ヒープを初期化する + // 注意: ヒープ内の全要素を反転し、最大ヒープで最小ヒープをシミュレートする + const maxHeap = new MaxHeap([]); + // 配列の先頭 k 個の要素をヒープに追加 + for (let i = 0; i < k; i++) { + pushMinHeap(maxHeap, nums[i]); + } + // k+1 番目の要素から開始し、ヒープ長を k に保つ + for (let i = k; i < nums.length; i++) { + // 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する + if (nums[i] > peekMinHeap(maxHeap)) { + popMinHeap(maxHeap); + pushMinHeap(maxHeap, nums[i]); + } + } + // ヒープ内の要素を返す + return getMinHeap(maxHeap); +} + +/* Driver Code */ +const nums = [1, 7, 6, 3, 2]; +const k = 3; +const res = topKHeap(nums, k); +console.log(`最大の ${k} 個の要素は`, res); diff --git a/ja/codes/javascript/chapter_searching/binary_search.js b/ja/codes/javascript/chapter_searching/binary_search.js new file mode 100644 index 000000000..3cfb5745f --- /dev/null +++ b/ja/codes/javascript/chapter_searching/binary_search.js @@ -0,0 +1,60 @@ +/** + * File: binary_search.js + * Created Time: 2022-12-22 + * Author: JoseHung (szhong@link.cuhk.edu.hk) + */ + +/* 二分探索(両閉区間) */ +function binarySearch(nums, target) { + // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す + let i = 0, + j = nums.length - 1; + // ループし、探索区間が空になったら終了する(i > j で空) + while (i <= j) { + // 中点インデックス `m` を計算し、`parseInt()` で切り捨てる + const m = parseInt(i + (j - i) / 2); + if (nums[m] < target) + // この場合、target は区間 [m+1, j] にある + i = m + 1; + else if (nums[m] > target) + // この場合、target は区間 [i, m-1] にある + j = m - 1; + else return m; // 目標要素が見つかったらそのインデックスを返す + } + // 目標要素が見つからなければ -1 を返す + return -1; +} + +/* 二分探索(左閉右開区間) */ +function binarySearchLCRO(nums, target) { + // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す + let i = 0, + j = nums.length; + // ループし、探索区間が空になったら終了する(i = j で空) + while (i < j) { + // 中点インデックス `m` を計算し、`parseInt()` で切り捨てる + const m = parseInt(i + (j - i) / 2); + if (nums[m] < target) + // この場合、target は区間 [m+1, j) にある + i = m + 1; + else if (nums[m] > target) + // この場合、target は区間 [i, m) にある + j = m; + // 目標要素が見つかったらそのインデックスを返す + else return m; + } + // 目標要素が見つからなければ -1 を返す + return -1; +} + +/* Driver Code */ +const target = 6; +const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + +/* 二分探索(両閉区間) */ +let index = binarySearch(nums, target); +console.log('目標要素 6 のインデックス = ' + index); + +/* 二分探索(左閉右開区間) */ +index = binarySearchLCRO(nums, target); +console.log('目標要素 6 のインデックス = ' + index); diff --git a/ja/codes/javascript/chapter_searching/binary_search_edge.js b/ja/codes/javascript/chapter_searching/binary_search_edge.js new file mode 100644 index 000000000..4fbf735a5 --- /dev/null +++ b/ja/codes/javascript/chapter_searching/binary_search_edge.js @@ -0,0 +1,45 @@ +/** + * File: binary_search_edge.js + * Created Time: 2023-08-22 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +const { binarySearchInsertion } = require('./binary_search_insertion.js'); + +/* 最も左の target を二分探索 */ +function binarySearchLeftEdge(nums, target) { + // target の挿入位置を探すのと等価 + const i = binarySearchInsertion(nums, target); + // target が見つからなければ、-1 を返す + if (i === nums.length || nums[i] !== target) { + return -1; + } + // target が見つかったら、インデックス i を返す + return i; +} + +/* 最も右の target を二分探索 */ +function binarySearchRightEdge(nums, target) { + // 最左の target + 1 を探す問題に変換する + const i = binarySearchInsertion(nums, target + 1); + // j は最も右の target を指し、i は target より大きい最初の要素を指す + const j = i - 1; + // target が見つからなければ、-1 を返す + if (j === -1 || nums[j] !== target) { + return -1; + } + // target が見つかったら、インデックス j を返す + return j; +} + +/* Driver Code */ +// 重複要素を含む配列 +const nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; +console.log('\n配列 nums = ' + nums); +// 二分探索で左端と右端を探す +for (const target of [6, 7]) { + let index = binarySearchLeftEdge(nums, target); + console.log('最も左の要素 ' + target + ' のインデックスは ' + index); + index = binarySearchRightEdge(nums, target); + console.log('最も右の要素 ' + target + ' のインデックスは ' + index); +} diff --git a/ja/codes/javascript/chapter_searching/binary_search_insertion.js b/ja/codes/javascript/chapter_searching/binary_search_insertion.js new file mode 100644 index 000000000..e0b0a89f3 --- /dev/null +++ b/ja/codes/javascript/chapter_searching/binary_search_insertion.js @@ -0,0 +1,64 @@ +/** + * File: binary_search_insertion.js + * Created Time: 2023-08-22 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 二分探索で挿入位置を探す(重複要素なし) */ +function binarySearchInsertionSimple(nums, target) { + let i = 0, + j = nums.length - 1; // 両閉区間 [0, n-1] を初期化 + while (i <= j) { + const m = Math.floor(i + (j - i) / 2); // 中点インデックス m を計算し、Math.floor() で切り捨てる + if (nums[m] < target) { + i = m + 1; // target は区間 [m+1, j] にある + } else if (nums[m] > target) { + j = m - 1; // target は区間 [i, m-1] にある + } else { + return m; // target が見つかったら、挿入位置 m を返す + } + } + // target が見つからなければ、挿入位置 i を返す + return i; +} + +/* 二分探索で挿入位置を探す(重複要素あり) */ +function binarySearchInsertion(nums, target) { + let i = 0, + j = nums.length - 1; // 両閉区間 [0, n-1] を初期化 + while (i <= j) { + const m = Math.floor(i + (j - i) / 2); // 中点インデックス m を計算し、Math.floor() で切り捨てる + if (nums[m] < target) { + i = m + 1; // target は区間 [m+1, j] にある + } else if (nums[m] > target) { + j = m - 1; // target は区間 [i, m-1] にある + } else { + j = m - 1; // target より小さい最初の要素は区間 [i, m-1] にある + } + } + // 挿入位置 i を返す + return i; +} + +/* Driver Code */ +// 重複要素のない配列 +let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; +console.log('\n配列 nums = ' + nums); +// 二分探索で挿入位置を探す +for (const target of [6, 9]) { + const index = binarySearchInsertionSimple(nums, target); + console.log('要素 ' + target + ' の挿入位置のインデックスは ' + index); +} + +// 重複要素を含む配列 +nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; +console.log('\n配列 nums = ' + nums); +// 二分探索で挿入位置を探す +for (const target of [2, 6, 20]) { + const index = binarySearchInsertion(nums, target); + console.log('要素 ' + target + ' の挿入位置のインデックスは ' + index); +} + +module.exports = { + binarySearchInsertion, +}; diff --git a/ja/codes/javascript/chapter_searching/hashing_search.js b/ja/codes/javascript/chapter_searching/hashing_search.js new file mode 100644 index 000000000..897972d04 --- /dev/null +++ b/ja/codes/javascript/chapter_searching/hashing_search.js @@ -0,0 +1,45 @@ +/** + * File: hashing_search.js + * Created Time: 2022-12-29 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +const { arrToLinkedList } = require('../modules/ListNode'); + +/* ハッシュ探索(配列) */ +function hashingSearchArray(map, target) { + // ハッシュテーブルの key: 目標要素、value: インデックス + // ハッシュテーブルにこの key がなければ -1 を返す + return map.has(target) ? map.get(target) : -1; +} + +/* ハッシュ探索(連結リスト) */ +function hashingSearchLinkedList(map, target) { + // ハッシュテーブルの key: 目標ノード値、value: ノードオブジェクト + // ハッシュテーブルにこの key がなければ null を返す + return map.has(target) ? map.get(target) : null; +} + +/* Driver Code */ +const target = 3; + +/* ハッシュ探索(配列) */ +const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; +// ハッシュテーブルを初期化 +const map = new Map(); +for (let i = 0; i < nums.length; i++) { + map.set(nums[i], i); // key: 要素、value: インデックス +} +const index = hashingSearchArray(map, target); +console.log('目標要素 3 のインデックス = ' + index); + +/* ハッシュ探索(連結リスト) */ +let head = arrToLinkedList(nums); +// ハッシュテーブルを初期化 +const map1 = new Map(); +while (head != null) { + map1.set(head.val, head); // key: ノード値、value: ノード + head = head.next; +} +const node = hashingSearchLinkedList(map1, target); +console.log('目標ノード値 3 に対応するノードオブジェクトは', node); diff --git a/ja/codes/javascript/chapter_searching/linear_search.js b/ja/codes/javascript/chapter_searching/linear_search.js new file mode 100644 index 000000000..ade93ddde --- /dev/null +++ b/ja/codes/javascript/chapter_searching/linear_search.js @@ -0,0 +1,47 @@ +/** + * File: linear_search.js + * Created Time: 2022-12-22 + * Author: JoseHung (szhong@link.cuhk.edu.hk) + */ + +const { ListNode, arrToLinkedList } = require('../modules/ListNode'); + +/* 線形探索(配列) */ +function linearSearchArray(nums, target) { + // 配列を走査 + for (let i = 0; i < nums.length; i++) { + // 目標要素が見つかったらそのインデックスを返す + if (nums[i] === target) { + return i; + } + } + // 目標要素が見つからなければ -1 を返す + return -1; +} + +/* 線形探索(連結リスト) */ +function linearSearchLinkedList(head, target) { + // 連結リストを走査 + while (head) { + // 対象ノードが見つかったら、それを返す + if (head.val === target) { + return head; + } + head = head.next; + } + // 対象ノードが見つからない場合は null を返す + return null; +} + +/* Driver Code */ +const target = 3; + +/* 配列で線形探索を行う */ +const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; +const index = linearSearchArray(nums, target); +console.log('目標要素 3 のインデックス = ' + index); + +/* 連結リストで線形探索を行う */ +const head = arrToLinkedList(nums); +const node = linearSearchLinkedList(head, target); +console.log('目標ノード値 3 に対応するノードオブジェクトは ', node); diff --git a/ja/codes/javascript/chapter_searching/two_sum.js b/ja/codes/javascript/chapter_searching/two_sum.js new file mode 100644 index 000000000..c4ae17492 --- /dev/null +++ b/ja/codes/javascript/chapter_searching/two_sum.js @@ -0,0 +1,46 @@ +/** + * File: two_sum.js + * Created Time: 2022-12-15 + * Author: gyt95 (gytkwan@gmail.com) + */ + +/* 方法 1:総当たり列挙 */ +function twoSumBruteForce(nums, target) { + const n = nums.length; + // 2重ループのため、時間計算量は O(n^2) + for (let i = 0; i < n; i++) { + for (let j = i + 1; j < n; j++) { + if (nums[i] + nums[j] === target) { + return [i, j]; + } + } + } + return []; +} + +/* 方法 2:補助ハッシュテーブル */ +function twoSumHashTable(nums, target) { + // 補助ハッシュテーブルを使用し、空間計算量は O(n) + let m = {}; + // 単一ループで、時間計算量は O(n) + for (let i = 0; i < nums.length; i++) { + if (m[target - nums[i]] !== undefined) { + return [m[target - nums[i]], i]; + } else { + m[nums[i]] = i; + } + } + return []; +} + +/* Driver Code */ +// 方法 1 +const nums = [2, 7, 11, 15], + target = 13; + +let res = twoSumBruteForce(nums, target); +console.log('方法1 res = ', res); + +// 方法 2 +res = twoSumHashTable(nums, target); +console.log('方法2 res = ', res); diff --git a/ja/codes/javascript/chapter_sorting/bubble_sort.js b/ja/codes/javascript/chapter_sorting/bubble_sort.js new file mode 100644 index 000000000..3d1db7186 --- /dev/null +++ b/ja/codes/javascript/chapter_sorting/bubble_sort.js @@ -0,0 +1,49 @@ +/** + * File: bubble_sort.js + * Created Time: 2022-12-01 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* バブルソート */ +function bubbleSort(nums) { + // 外側のループ:未ソート区間は [0, i] + for (let i = nums.length - 1; i > 0; i--) { + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // nums[j] と nums[j + 1] を交換 + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } +} + +/* バブルソート(フラグ最適化) */ +function bubbleSortWithFlag(nums) { + // 外側のループ:未ソート区間は [0, i] + for (let i = nums.length - 1; i > 0; i--) { + let flag = false; // フラグを初期化する + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // nums[j] と nums[j + 1] を交換 + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; // 交換する要素を記録 + } + } + if (!flag) break; // このバブル処理で要素交換が一度もなければそのまま終了 + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +bubbleSort(nums); +console.log('バブルソート完了後 nums =', nums); + +const nums1 = [4, 1, 3, 1, 5, 2]; +bubbleSortWithFlag(nums1); +console.log('バブルソート完了後 nums =', nums1); diff --git a/ja/codes/javascript/chapter_sorting/bucket_sort.js b/ja/codes/javascript/chapter_sorting/bucket_sort.js new file mode 100644 index 000000000..90b697ecc --- /dev/null +++ b/ja/codes/javascript/chapter_sorting/bucket_sort.js @@ -0,0 +1,39 @@ +/** + * File: bucket_sort.js + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* バケットソート */ +function bucketSort(nums) { + // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする + const k = nums.length / 2; + const buckets = []; + for (let i = 0; i < k; i++) { + buckets.push([]); + } + // 1. 配列要素を各バケットに振り分ける + for (const num of nums) { + // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する + const i = Math.floor(num * k); + // num をバケット i に追加 + buckets[i].push(num); + } + // 2. 各バケットをソートする + for (const bucket of buckets) { + // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい + bucket.sort((a, b) => a - b); + } + // 3. バケットを走査して結果を結合 + let i = 0; + for (const bucket of buckets) { + for (const num of bucket) { + nums[i++] = num; + } + } +} + +/* Driver Code */ +const nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; +bucketSort(nums); +console.log('バケットソート完了後 nums =', nums); diff --git a/ja/codes/javascript/chapter_sorting/counting_sort.js b/ja/codes/javascript/chapter_sorting/counting_sort.js new file mode 100644 index 000000000..e3b1587aa --- /dev/null +++ b/ja/codes/javascript/chapter_sorting/counting_sort.js @@ -0,0 +1,65 @@ +/** + * File: counting_sort.js + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 計数ソート */ +// 簡易実装のため、オブジェクトのソートには使えない +function countingSortNaive(nums) { + // 1. 配列の最大要素 m を求める + let m = Math.max(...nums); + // 2. 各数値の出現回数を数える + // counter[num] は num の出現回数を表す + const counter = new Array(m + 1).fill(0); + for (const num of nums) { + counter[num]++; + } + // 3. counter を走査し、各要素を元の配列 nums に書き戻す + let i = 0; + for (let num = 0; num < m + 1; num++) { + for (let j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } +} + +/* 計数ソート */ +// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである +function countingSort(nums) { + // 1. 配列の最大要素 m を求める + let m = Math.max(...nums); + // 2. 各数値の出現回数を数える + // counter[num] は num の出現回数を表す + const counter = new Array(m + 1).fill(0); + for (const num of nums) { + counter[num]++; + } + // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する + // つまり counter[num]-1 は、num が res に最後に現れるインデックス + for (let i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. nums を逆順に走査し、各要素を結果配列 res に格納する + // 結果を記録するための配列 res を初期化 + const n = nums.length; + const res = new Array(n); + for (let i = n - 1; i >= 0; i--) { + const num = nums[i]; + res[counter[num] - 1] = num; // num を対応するインデックスに配置 + counter[num]--; // 累積和を 1 減らして、次に num を配置するインデックスを得る + } + // 結果配列 res で元の配列 nums を上書きする + for (let i = 0; i < n; i++) { + nums[i] = res[i]; + } +} + +/* Driver Code */ +const nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; +countingSortNaive(nums); +console.log('カウントソート(オブジェクトはソート不可)完了後 nums =', nums); + +const nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; +countingSort(nums1); +console.log('カウントソート完了後 nums1 =', nums1); diff --git a/ja/codes/javascript/chapter_sorting/heap_sort.js b/ja/codes/javascript/chapter_sorting/heap_sort.js new file mode 100644 index 000000000..fca079fd9 --- /dev/null +++ b/ja/codes/javascript/chapter_sorting/heap_sort.js @@ -0,0 +1,49 @@ +/** + * File: heap_sort.js + * Created Time: 2023-06-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* ヒープの長さは n。ノード i から下方向にヒープ化 */ +function siftDown(nums, n, i) { + while (true) { + // ノード i, l, r のうち値が最大のノードを ma とする + let l = 2 * i + 1; + let r = 2 * i + 2; + let ma = i; + if (l < n && nums[l] > nums[ma]) { + ma = l; + } + if (r < n && nums[r] > nums[ma]) { + ma = r; + } + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + if (ma === i) { + break; + } + // 2 つのノードを交換 + [nums[i], nums[ma]] = [nums[ma], nums[i]]; + // ループで上から下へヒープ化 + i = ma; + } +} + +/* ヒープソート */ +function heapSort(nums) { + // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する + for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) { + siftDown(nums, nums.length, i); + } + // ヒープから最大要素を取り出し、n-1 回繰り返す + for (let i = nums.length - 1; i > 0; i--) { + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + [nums[0], nums[i]] = [nums[i], nums[0]]; + // 根ノードを起点に、上から下へヒープ化 + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +heapSort(nums); +console.log('ヒープソート完了後 nums =', nums); diff --git a/ja/codes/javascript/chapter_sorting/insertion_sort.js b/ja/codes/javascript/chapter_sorting/insertion_sort.js new file mode 100644 index 000000000..9e168a613 --- /dev/null +++ b/ja/codes/javascript/chapter_sorting/insertion_sort.js @@ -0,0 +1,25 @@ +/** + * File: insertion_sort.js + * Created Time: 2022-12-01 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 挿入ソート */ +function insertionSort(nums) { + // 外側ループ:整列済み区間は [0, i-1] + for (let i = 1; i < nums.length; i++) { + let base = nums[i], + j = i - 1; + // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // nums[j] を 1 つ右へ移動する + j--; + } + nums[j + 1] = base; // base を正しい位置に配置する + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +insertionSort(nums); +console.log('挿入ソート完了後 nums =', nums); diff --git a/ja/codes/javascript/chapter_sorting/merge_sort.js b/ja/codes/javascript/chapter_sorting/merge_sort.js new file mode 100644 index 000000000..51cfb9703 --- /dev/null +++ b/ja/codes/javascript/chapter_sorting/merge_sort.js @@ -0,0 +1,52 @@ +/** + * File: merge_sort.js + * Created Time: 2022-12-01 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 左部分配列と右部分配列をマージ */ +function merge(nums, left, mid, right) { + // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] + // マージ結果を格納する一時配列 tmp を作成 + const tmp = new Array(right - left + 1); + // 左右の部分配列の開始インデックスを初期化する + let i = left, + j = mid + 1, + k = 0; + // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) { + tmp[k++] = nums[i++]; + } else { + tmp[k++] = nums[j++]; + } + } + // 左右の部分配列の残り要素を一時配列にコピーする + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする + for (k = 0; k < tmp.length; k++) { + nums[left + k] = tmp[k]; + } +} + +/* マージソート */ +function mergeSort(nums, left, right) { + // 終了条件 + if (left >= right) return; // 部分配列の長さが 1 になったら再帰を終了 + // 分割フェーズ + let mid = Math.floor(left + (right - left) / 2); // 中点を計算 + mergeSort(nums, left, mid); // 左部分配列を再帰処理 + mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理 + // マージフェーズ + merge(nums, left, mid, right); +} + +/* Driver Code */ +const nums = [7, 3, 2, 6, 0, 1, 5, 4]; +mergeSort(nums, 0, nums.length - 1); +console.log('マージソート完了後 nums =', nums); diff --git a/ja/codes/javascript/chapter_sorting/quick_sort.js b/ja/codes/javascript/chapter_sorting/quick_sort.js new file mode 100644 index 000000000..bfa4a8f1f --- /dev/null +++ b/ja/codes/javascript/chapter_sorting/quick_sort.js @@ -0,0 +1,161 @@ +/** + * File: quick_sort.js + * Created Time: 2022-12-01 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* クイックソートクラス */ +class QuickSort { + /* 要素の交換 */ + swap(nums, i, j) { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 番兵分割 */ + partition(nums, left, right) { + // nums[left] を基準値とする + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j -= 1; // 右から左へ基準値未満の最初の要素を探す + } + while (i < j && nums[i] <= nums[left]) { + i += 1; // 左から右へ基準値より大きい最初の要素を探す + } + // 要素の交換 + this.swap(nums, i, j); // この 2 つの要素を交換 + } + this.swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する + return i; // 基準値のインデックスを返す + } + + /* クイックソート */ + quickSort(nums, left, right) { + // 部分配列の長さが 1 なら再帰を終了する + if (left >= right) return; + // 番兵分割 + const pivot = this.partition(nums, left, right); + // 左右の部分配列を再帰処理 + this.quickSort(nums, left, pivot - 1); + this.quickSort(nums, pivot + 1, right); + } +} + +/* クイックソートクラス(中央値ピボット最適化) */ +class QuickSortMedian { + /* 要素の交換 */ + swap(nums, i, j) { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 3つの候補要素の中央値を選ぶ */ + medianThree(nums, left, mid, right) { + let l = nums[left], + m = nums[mid], + r = nums[right]; + // m は l と r の間 + if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; + // l は m と r の間 + if ((m <= l && l <= r) || (r <= l && l <= m)) return left; + return right; + } + + /* 番兵による分割処理(3 点中央値) */ + partition(nums, left, right) { + // 3つの候補要素の中央値を選ぶ + let med = this.medianThree( + nums, + left, + Math.floor((left + right) / 2), + right + ); + // 中央値を配列の最左端に交換する + this.swap(nums, left, med); + // nums[left] を基準値とする + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) j--; // 右から左へ基準値未満の最初の要素を探す + while (i < j && nums[i] <= nums[left]) i++; // 左から右へ基準値より大きい最初の要素を探す + this.swap(nums, i, j); // この 2 つの要素を交換 + } + this.swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する + return i; // 基準値のインデックスを返す + } + + /* クイックソート */ + quickSort(nums, left, right) { + // 部分配列の長さが 1 なら再帰を終了する + if (left >= right) return; + // 番兵分割 + const pivot = this.partition(nums, left, right); + // 左右の部分配列を再帰処理 + this.quickSort(nums, left, pivot - 1); + this.quickSort(nums, pivot + 1, right); + } +} + +/* クイックソートクラス(再帰深度最適化) */ +class QuickSortTailCall { + /* 要素の交換 */ + swap(nums, i, j) { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 番兵分割 */ + partition(nums, left, right) { + // nums[left] を基準値とする + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) j--; // 右から左へ基準値未満の最初の要素を探す + while (i < j && nums[i] <= nums[left]) i++; // 左から右へ基準値より大きい最初の要素を探す + this.swap(nums, i, j); // この 2 つの要素を交換 + } + this.swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する + return i; // 基準値のインデックスを返す + } + + /* クイックソート(再帰深度最適化) */ + quickSort(nums, left, right) { + // 部分配列の長さが 1 なら終了 + while (left < right) { + // 番兵による分割処理 + let pivot = this.partition(nums, left, right); + // 2 つの部分配列のうち短いほうにクイックソートを適用する + if (pivot - left < right - pivot) { + this.quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソート + left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right] + } else { + this.quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソート + right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1] + } + } + } +} + +/* Driver Code */ +/* クイックソート */ +const nums = [2, 4, 1, 0, 3, 5]; +const quickSort = new QuickSort(); +quickSort.quickSort(nums, 0, nums.length - 1); +console.log('クイックソート完了後 nums =', nums); + +/* クイックソート(中央値の基準値で最適化) */ +const nums1 = [2, 4, 1, 0, 3, 5]; +const quickSortMedian = new QuickSortMedian(); +quickSortMedian.quickSort(nums1, 0, nums1.length - 1); +console.log('クイックソート(中央値ピボット最適化)完了後 nums =', nums1); + +/* クイックソート(再帰深度最適化) */ +const nums2 = [2, 4, 1, 0, 3, 5]; +const quickSortTailCall = new QuickSortTailCall(); +quickSortTailCall.quickSort(nums2, 0, nums2.length - 1); +console.log('クイックソート(再帰深度最適化)完了後 nums =', nums2); diff --git a/ja/codes/javascript/chapter_sorting/radix_sort.js b/ja/codes/javascript/chapter_sorting/radix_sort.js new file mode 100644 index 000000000..e3172d498 --- /dev/null +++ b/ja/codes/javascript/chapter_sorting/radix_sort.js @@ -0,0 +1,61 @@ +/** + * File: radix_sort.js + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */ +function digit(num, exp) { + // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す + return Math.floor(num / exp) % 10; +} + +/* 計数ソート(nums の k 桁目でソート) */ +function countingSortDigit(nums, exp) { + // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 + const counter = new Array(10).fill(0); + const n = nums.length; + // 0~9 の各数字の出現回数を集計する + for (let i = 0; i < n; i++) { + const d = digit(nums[i], exp); // nums[i] の第 k 位を取得し、d とする + counter[d]++; // 数字 d の出現回数を数える + } + // 累積和を求め、「出現回数」を「配列インデックス」に変換する + for (let i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する + const res = new Array(n).fill(0); + for (let i = n - 1; i >= 0; i--) { + const d = digit(nums[i], exp); + const j = counter[d] - 1; // d の配列内インデックス j を取得する + res[j] = nums[i]; // 現在の要素をインデックス j に格納する + counter[d]--; // d の個数を 1 減らす + } + // 結果で元の配列 nums を上書きする + for (let i = 0; i < n; i++) { + nums[i] = res[i]; + } +} + +/* 基数ソート */ +function radixSort(nums) { + // 最大桁数の判定用に配列の最大要素を取得 + let m = Math.max(... nums); + // 下位桁から上位桁の順に走査する + for (let exp = 1; exp <= m; exp *= 10) { + // 配列要素の k 桁目に対して計数ソートを行う + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // つまり exp = 10^(k-1) + countingSortDigit(nums, exp); + } +} + +/* Driver Code */ +const nums = [ + 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, + 30524779, 82060337, 63832996, +]; +radixSort(nums); +console.log('基数ソート完了後 nums =', nums); diff --git a/ja/codes/javascript/chapter_sorting/selection_sort.js b/ja/codes/javascript/chapter_sorting/selection_sort.js new file mode 100644 index 000000000..9f9909294 --- /dev/null +++ b/ja/codes/javascript/chapter_sorting/selection_sort.js @@ -0,0 +1,27 @@ +/** + * File: selection_sort.js + * Created Time: 2023-06-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 選択ソート */ +function selectionSort(nums) { + let n = nums.length; + // 外側ループ:未整列区間は [i, n-1] + for (let i = 0; i < n - 1; i++) { + // 内側のループ:未ソート区間の最小要素を見つける + let k = i; + for (let j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) { + k = j; // 最小要素のインデックスを記録 + } + } + // その最小要素を未整列区間の先頭要素と交換する + [nums[i], nums[k]] = [nums[k], nums[i]]; + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +selectionSort(nums); +console.log('選択ソート完了後 nums =', nums); diff --git a/ja/codes/javascript/chapter_stack_and_queue/array_deque.js b/ja/codes/javascript/chapter_stack_and_queue/array_deque.js new file mode 100644 index 000000000..196f3d200 --- /dev/null +++ b/ja/codes/javascript/chapter_stack_and_queue/array_deque.js @@ -0,0 +1,156 @@ +/** + * File: array_deque.js + * Created Time: 2023-02-28 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 循環配列ベースの両端キュー */ +class ArrayDeque { + #nums; // 両端キューの要素を格納する配列 + #front; // 先頭ポインタ。先頭要素を指す + #queSize; // 両端キューの長さ + + /* コンストラクタ */ + constructor(capacity) { + this.#nums = new Array(capacity); + this.#front = 0; + this.#queSize = 0; + } + + /* 両端キューの容量を取得 */ + capacity() { + return this.#nums.length; + } + + /* 両端キューの長さを取得 */ + size() { + return this.#queSize; + } + + /* 両端キューが空かどうかを判定 */ + isEmpty() { + return this.#queSize === 0; + } + + /* 循環配列のインデックスを計算 */ + index(i) { + // 剰余演算により配列の先頭と末尾をつなげる + // i が配列の末尾を越えたら先頭に戻る + // i が配列の先頭を越えて前に出たら末尾に戻る + return (i + this.capacity()) % this.capacity(); + } + + /* キュー先頭にエンキュー */ + pushFirst(num) { + if (this.#queSize === this.capacity()) { + console.log('両端キューがいっぱいです'); + return; + } + // 先頭ポインタを左に 1 つ移動する + // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする + this.#front = this.index(this.#front - 1); + // num をキュー先頭に追加 + this.#nums[this.#front] = num; + this.#queSize++; + } + + /* キュー末尾にエンキュー */ + pushLast(num) { + if (this.#queSize === this.capacity()) { + console.log('両端キューがいっぱいです'); + return; + } + // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す + const rear = this.index(this.#front + this.#queSize); + // num をキュー末尾に追加 + this.#nums[rear] = num; + this.#queSize++; + } + + /* キュー先頭からデキュー */ + popFirst() { + const num = this.peekFirst(); + // 先頭ポインタを 1 つ後ろへ進める + this.#front = this.index(this.#front + 1); + this.#queSize--; + return num; + } + + /* キュー末尾からデキュー */ + popLast() { + const num = this.peekLast(); + this.#queSize--; + return num; + } + + /* キュー先頭の要素にアクセス */ + peekFirst() { + if (this.isEmpty()) throw new Error('The Deque Is Empty.'); + return this.#nums[this.#front]; + } + + /* キュー末尾の要素にアクセス */ + peekLast() { + if (this.isEmpty()) throw new Error('The Deque Is Empty.'); + // 末尾要素のインデックスを計算 + const last = this.index(this.#front + this.#queSize - 1); + return this.#nums[last]; + } + + /* 出力用の配列を返す */ + toArray() { + // 有効長の範囲内のリスト要素のみを変換 + const res = []; + for (let i = 0, j = this.#front; i < this.#queSize; i++, j++) { + res[i] = this.#nums[this.index(j)]; + } + return res; + } +} + +/* Driver Code */ +/* 両端キューを初期化 */ +const capacity = 5; +const deque = new ArrayDeque(capacity); +deque.pushLast(3); +deque.pushLast(2); +deque.pushLast(5); +console.log('両端キュー deque = [' + deque.toArray() + ']'); + +/* 要素にアクセス */ +const peekFirst = deque.peekFirst(); +console.log('先頭要素 peekFirst = ' + peekFirst); +const peekLast = deque.peekLast(); +console.log('末尾要素 peekLast = ' + peekLast); + +/* 要素をエンキュー */ +deque.pushLast(4); +console.log('要素 4 を末尾に追加した後 deque = [' + deque.toArray() + ']'); +deque.pushFirst(1); +console.log('要素 1 を先頭に追加した後 deque = [' + deque.toArray() + ']'); + +/* 要素をデキュー */ +const popLast = deque.popLast(); +console.log( + '末尾から取り出した要素 = ' + + popLast + + '、末尾から取り出した後 deque = [' + + deque.toArray() + + ']' +); +const popFirst = deque.popFirst(); +console.log( + '先頭から取り出した要素 = ' + + popFirst + + '、先頭から取り出した後 deque = [' + + deque.toArray() + + ']' +); + +/* 両端キューの長さを取得 */ +const size = deque.size(); +console.log('両端キューの長さ size = ' + size); + +/* 両端キューが空かどうかを判定 */ +const isEmpty = deque.isEmpty(); +console.log('両端キューが空かどうか = ' + isEmpty); diff --git a/ja/codes/javascript/chapter_stack_and_queue/array_queue.js b/ja/codes/javascript/chapter_stack_and_queue/array_queue.js new file mode 100644 index 000000000..344a9fb46 --- /dev/null +++ b/ja/codes/javascript/chapter_stack_and_queue/array_queue.js @@ -0,0 +1,106 @@ +/** + * File: array_queue.js + * Created Time: 2022-12-13 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* 循環配列ベースのキュー */ +class ArrayQueue { + #nums; // キュー要素を格納する配列 + #front = 0; // 先頭ポインタ。先頭要素を指す + #queSize = 0; // キューの長さ + + constructor(capacity) { + this.#nums = new Array(capacity); + } + + /* キューの容量を取得 */ + get capacity() { + return this.#nums.length; + } + + /* キューの長さを取得 */ + get size() { + return this.#queSize; + } + + /* キューが空かどうかを判定 */ + isEmpty() { + return this.#queSize === 0; + } + + /* エンキュー */ + push(num) { + if (this.size === this.capacity) { + console.log('キューがいっぱいです'); + return; + } + // 末尾ポインタを計算し、末尾インデックス + 1 を指す + // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする + const rear = (this.#front + this.size) % this.capacity; + // num をキュー末尾に追加 + this.#nums[rear] = num; + this.#queSize++; + } + + /* デキュー */ + pop() { + const num = this.peek(); + // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す + this.#front = (this.#front + 1) % this.capacity; + this.#queSize--; + return num; + } + + /* キュー先頭の要素にアクセス */ + peek() { + if (this.isEmpty()) throw new Error('キューが空です'); + return this.#nums[this.#front]; + } + + /* Array を返す */ + toArray() { + // 有効長の範囲内のリスト要素のみを変換 + const arr = new Array(this.size); + for (let i = 0, j = this.#front; i < this.size; i++, j++) { + arr[i] = this.#nums[j % this.capacity]; + } + return arr; + } +} + +/* Driver Code */ +/* キューを初期化 */ +const capacity = 10; +const queue = new ArrayQueue(capacity); + +/* 要素をエンキュー */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('キュー queue =', queue.toArray()); + +/* キュー先頭の要素にアクセス */ +const peek = queue.peek(); +console.log('先頭要素 peek = ' + peek); + +/* 要素をデキュー */ +const pop = queue.pop(); +console.log('取り出した要素 pop = ' + pop + '、取り出した後 queue =', queue.toArray()); + +/* キューの長さを取得 */ +const size = queue.size; +console.log('キューの長さ size = ' + size); + +/* キューが空かどうかを判定 */ +const isEmpty = queue.isEmpty(); +console.log('キューは空か = ' + isEmpty); + +/* 循環配列をテストする */ +for (let i = 0; i < 10; i++) { + queue.push(i); + queue.pop(); + console.log('第 ' + i + ' 回エンキュー + デキュー後 queue =', queue.toArray()); +} diff --git a/ja/codes/javascript/chapter_stack_and_queue/array_stack.js b/ja/codes/javascript/chapter_stack_and_queue/array_stack.js new file mode 100644 index 000000000..7d32139b3 --- /dev/null +++ b/ja/codes/javascript/chapter_stack_and_queue/array_stack.js @@ -0,0 +1,75 @@ +/** + * File: array_stack.js + * Created Time: 2022-12-09 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* 配列ベースのスタック */ +class ArrayStack { + #stack; + constructor() { + this.#stack = []; + } + + /* スタックの長さを取得 */ + get size() { + return this.#stack.length; + } + + /* スタックが空かどうかを判定 */ + isEmpty() { + return this.#stack.length === 0; + } + + /* プッシュ */ + push(num) { + this.#stack.push(num); + } + + /* ポップ */ + pop() { + if (this.isEmpty()) throw new Error('スタックが空'); + return this.#stack.pop(); + } + + /* スタックトップの要素にアクセス */ + top() { + if (this.isEmpty()) throw new Error('スタックが空'); + return this.#stack[this.#stack.length - 1]; + } + + /* Array を返す */ + toArray() { + return this.#stack; + } +} + +/* Driver Code */ +/* スタックを初期化 */ +const stack = new ArrayStack(); + +/* 要素をプッシュ */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('スタック stack = '); +console.log(stack.toArray()); + +/* スタックトップの要素にアクセス */ +const top = stack.top(); +console.log('スタックトップ要素 top = ' + top); + +/* 要素をポップ */ +const pop = stack.pop(); +console.log('ポップした要素 pop = ' + pop + ',ポップ後 stack = '); +console.log(stack.toArray()); + +/* スタックの長さを取得 */ +const size = stack.size; +console.log('スタックの長さ size = ' + size); + +/* 空かどうかを判定 */ +const isEmpty = stack.isEmpty(); +console.log('スタックは空か = ' + isEmpty); diff --git a/ja/codes/javascript/chapter_stack_and_queue/deque.js b/ja/codes/javascript/chapter_stack_and_queue/deque.js new file mode 100644 index 000000000..8f812cb9f --- /dev/null +++ b/ja/codes/javascript/chapter_stack_and_queue/deque.js @@ -0,0 +1,44 @@ +/** + * File: deque.js + * Created Time: 2023-01-17 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* Driver Code */ +/* 両端キューを初期化 */ +// JavaScript には組み込みの両端キューがないため、Array を両端キューとして使う +const deque = []; + +/* 要素をエンキュー */ +deque.push(2); +deque.push(5); +deque.push(4); +// 注意: 配列であるため、unshift() メソッドの時間計算量は O(n) +deque.unshift(3); +deque.unshift(1); +console.log('両端キュー deque = ', deque); + +/* 要素にアクセス */ +const peekFirst = deque[0]; +console.log('先頭要素 peekFirst = ' + peekFirst); +const peekLast = deque[deque.length - 1]; +console.log('末尾要素 peekLast = ' + peekLast); + +/* 要素をデキュー */ +// 注意: 配列であるため、shift() メソッドの時間計算量は O(n) +const popFront = deque.shift(); +console.log( + '先頭からデキューした要素 popFront = ' + popFront + ',先頭からデキュー後 deque = ' + deque +); +const popBack = deque.pop(); +console.log( + '末尾からデキューした要素 popBack = ' + popBack + ',末尾からデキュー後 deque = ' + deque +); + +/* 両端キューの長さを取得 */ +const size = deque.length; +console.log('両端キューの長さ size = ' + size); + +/* 両端キューが空かどうかを判定 */ +const isEmpty = size === 0; +console.log('両端キューが空かどうか = ' + isEmpty); diff --git a/ja/codes/javascript/chapter_stack_and_queue/linkedlist_deque.js b/ja/codes/javascript/chapter_stack_and_queue/linkedlist_deque.js new file mode 100644 index 000000000..bf76c8be0 --- /dev/null +++ b/ja/codes/javascript/chapter_stack_and_queue/linkedlist_deque.js @@ -0,0 +1,167 @@ +/** + * File: linkedlist_deque.js + * Created Time: 2023-02-04 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 双方向連結リストノード */ +class ListNode { + prev; // 前駆ノードへの参照(ポインタ) + next; // 後継ノードへの参照(ポインタ) + val; // ノード値 + + constructor(val) { + this.val = val; + this.next = null; + this.prev = null; + } +} + +/* 双方向連結リストベースの両端キュー */ +class LinkedListDeque { + #front; // 先頭ノード front + #rear; // 末尾ノード rear + #queSize; // 両端キューの長さ + + constructor() { + this.#front = null; + this.#rear = null; + this.#queSize = 0; + } + + /* 末尾へのエンキュー操作 */ + pushLast(val) { + const node = new ListNode(val); + // 連結リストが空なら、front と rear の両方を node に向ける + if (this.#queSize === 0) { + this.#front = node; + this.#rear = node; + } else { + // node を連結リストの末尾に追加 + this.#rear.next = node; + node.prev = this.#rear; + this.#rear = node; // 末尾ノードを更新する + } + this.#queSize++; + } + + /* 先頭へのエンキュー操作 */ + pushFirst(val) { + const node = new ListNode(val); + // 連結リストが空なら、front と rear の両方を node に向ける + if (this.#queSize === 0) { + this.#front = node; + this.#rear = node; + } else { + // node を連結リストの先頭に追加 + this.#front.prev = node; + node.next = this.#front; + this.#front = node; // 先頭ノードを更新する + } + this.#queSize++; + } + + /* キュー末尾からの取り出し */ + popLast() { + if (this.#queSize === 0) { + return null; + } + const value = this.#rear.val; // 末尾ノードの値を保存する + // 末尾ノードを削除 + let temp = this.#rear.prev; + if (temp !== null) { + temp.next = null; + this.#rear.prev = null; + } + this.#rear = temp; // 末尾ノードを更新する + this.#queSize--; + return value; + } + + /* キュー先頭からの取り出し */ + popFirst() { + if (this.#queSize === 0) { + return null; + } + const value = this.#front.val; // 末尾ノードの値を保存する + // 先頭ノードを削除 + let temp = this.#front.next; + if (temp !== null) { + temp.prev = null; + this.#front.next = null; + } + this.#front = temp; // 先頭ノードを更新する + this.#queSize--; + return value; + } + + /* キュー末尾の要素にアクセス */ + peekLast() { + return this.#queSize === 0 ? null : this.#rear.val; + } + + /* キュー先頭の要素にアクセス */ + peekFirst() { + return this.#queSize === 0 ? null : this.#front.val; + } + + /* 両端キューの長さを取得 */ + size() { + return this.#queSize; + } + + /* 両端キューが空かどうかを判定 */ + isEmpty() { + return this.#queSize === 0; + } + + /* 両端キューを出力する */ + print() { + const arr = []; + let temp = this.#front; + while (temp !== null) { + arr.push(temp.val); + temp = temp.next; + } + console.log('[' + arr.join(', ') + ']'); + } +} + +/* Driver Code */ +/* 両端キューを初期化 */ +const linkedListDeque = new LinkedListDeque(); +linkedListDeque.pushLast(3); +linkedListDeque.pushLast(2); +linkedListDeque.pushLast(5); +console.log('両端キュー linkedListDeque = '); +linkedListDeque.print(); + +/* 要素にアクセス */ +const peekFirst = linkedListDeque.peekFirst(); +console.log('先頭要素 peekFirst = ' + peekFirst); +const peekLast = linkedListDeque.peekLast(); +console.log('末尾要素 peekLast = ' + peekLast); + +/* 要素をエンキュー */ +linkedListDeque.pushLast(4); +console.log('要素 4 を末尾にエンキュー後 linkedListDeque = '); +linkedListDeque.print(); +linkedListDeque.pushFirst(1); +console.log('要素 1 を先頭にエンキュー後 linkedListDeque = '); +linkedListDeque.print(); + +/* 要素をデキュー */ +const popLast = linkedListDeque.popLast(); +console.log('末尾からデキューした要素 = ' + popLast + ',末尾からデキュー後 linkedListDeque = '); +linkedListDeque.print(); +const popFirst = linkedListDeque.popFirst(); +console.log('先頭からデキューした要素 = ' + popFirst + ',先頭からデキュー後 linkedListDeque = '); +linkedListDeque.print(); + +/* 両端キューの長さを取得 */ +const size = linkedListDeque.size(); +console.log('両端キューの長さ size = ' + size); + +/* 両端キューが空かどうかを判定 */ +const isEmpty = linkedListDeque.isEmpty(); +console.log('両端キューが空かどうか = ' + isEmpty); diff --git a/ja/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js b/ja/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js new file mode 100644 index 000000000..f40599967 --- /dev/null +++ b/ja/codes/javascript/chapter_stack_and_queue/linkedlist_queue.js @@ -0,0 +1,99 @@ +/** + * File: linkedlist_queue.js + * Created Time: 2022-12-20 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +const { ListNode } = require('../modules/ListNode'); + +/* 連結リストベースのキュー */ +class LinkedListQueue { + #front; // 先頭ノード #front + #rear; // 末尾ノード #rear + #queSize = 0; + + constructor() { + this.#front = null; + this.#rear = null; + } + + /* キューの長さを取得 */ + get size() { + return this.#queSize; + } + + /* キューが空かどうかを判定 */ + isEmpty() { + return this.size === 0; + } + + /* エンキュー */ + push(num) { + // 末尾ノードの後ろに num を追加 + const node = new ListNode(num); + // キューが空なら、先頭・末尾ノードをともにそのノードに設定 + if (!this.#front) { + this.#front = node; + this.#rear = node; + // キューが空でなければ、そのノードを末尾ノードの後ろに追加 + } else { + this.#rear.next = node; + this.#rear = node; + } + this.#queSize++; + } + + /* デキュー */ + pop() { + const num = this.peek(); + // 先頭ノードを削除 + this.#front = this.#front.next; + this.#queSize--; + return num; + } + + /* キュー先頭の要素にアクセス */ + peek() { + if (this.size === 0) throw new Error('キューが空'); + return this.#front.val; + } + + /* 連結リストを Array に変換して返す */ + toArray() { + let node = this.#front; + const res = new Array(this.size); + for (let i = 0; i < res.length; i++) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +/* Driver Code */ +/* キューを初期化 */ +const queue = new LinkedListQueue(); + +/* 要素をエンキュー */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('キュー queue = ' + queue.toArray()); + +/* キュー先頭の要素にアクセス */ +const peek = queue.peek(); +console.log('先頭要素 peek = ' + peek); + +/* 要素をデキュー */ +const pop = queue.pop(); +console.log('デキューした要素 pop = ' + pop + ',デキュー後 queue = ' + queue.toArray()); + +/* キューの長さを取得 */ +const size = queue.size; +console.log('キューの長さ size = ' + size); + +/* キューが空かどうかを判定 */ +const isEmpty = queue.isEmpty(); +console.log('キューは空か = ' + isEmpty); diff --git a/ja/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js b/ja/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js new file mode 100644 index 000000000..b07cd326a --- /dev/null +++ b/ja/codes/javascript/chapter_stack_and_queue/linkedlist_stack.js @@ -0,0 +1,88 @@ +/** + * File: linkedlist_stack.js + * Created Time: 2022-12-22 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +const { ListNode } = require('../modules/ListNode'); + +/* 連結リストベースのスタック */ +class LinkedListStack { + #stackPeek; // 先頭ノードをスタックトップとする + #stkSize = 0; // スタックの長さ + + constructor() { + this.#stackPeek = null; + } + + /* スタックの長さを取得 */ + get size() { + return this.#stkSize; + } + + /* スタックが空かどうかを判定 */ + isEmpty() { + return this.size === 0; + } + + /* プッシュ */ + push(num) { + const node = new ListNode(num); + node.next = this.#stackPeek; + this.#stackPeek = node; + this.#stkSize++; + } + + /* ポップ */ + pop() { + const num = this.peek(); + this.#stackPeek = this.#stackPeek.next; + this.#stkSize--; + return num; + } + + /* スタックトップの要素にアクセス */ + peek() { + if (!this.#stackPeek) throw new Error('スタックが空'); + return this.#stackPeek.val; + } + + /* 連結リストを Array に変換して返す */ + toArray() { + let node = this.#stackPeek; + const res = new Array(this.size); + for (let i = res.length - 1; i >= 0; i--) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +/* Driver Code */ +/* スタックを初期化 */ +const stack = new LinkedListStack(); + +/* 要素をプッシュ */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('スタック stack = ' + stack.toArray()); + +/* スタックトップの要素にアクセス */ +const peek = stack.peek(); +console.log('スタックトップ要素 peek = ' + peek); + +/* 要素をポップ */ +const pop = stack.pop(); +console.log('ポップした要素 pop = ' + pop + ',ポップ後 stack = ' + stack.toArray()); + +/* スタックの長さを取得 */ +const size = stack.size; +console.log('スタックの長さ size = ' + size); + +/* 空かどうかを判定 */ +const isEmpty = stack.isEmpty(); +console.log('スタックは空か = ' + isEmpty); diff --git a/ja/codes/javascript/chapter_stack_and_queue/queue.js b/ja/codes/javascript/chapter_stack_and_queue/queue.js new file mode 100644 index 000000000..28c759d6f --- /dev/null +++ b/ja/codes/javascript/chapter_stack_and_queue/queue.js @@ -0,0 +1,35 @@ +/** + * File: queue.js + * Created Time: 2022-12-05 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* Driver Code */ +/* キューを初期化 */ +// JavaScript には組み込みのキューがないため、Array をキューとして使う +const queue = []; + +/* 要素をエンキュー */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('キュー queue =', queue); + +/* キュー先頭の要素にアクセス */ +const peek = queue[0]; +console.log('先頭要素 peek =', peek); + +/* 要素をデキュー */ +// 基盤が配列であるため、shift() メソッドの時間計算量は O(n) +const pop = queue.shift(); +console.log('デキューした要素 pop =', pop, ',デキュー後 queue = ', queue); + +/* キューの長さを取得 */ +const size = queue.length; +console.log('キューの長さ size =', size); + +/* キューが空かどうかを判定 */ +const isEmpty = queue.length === 0; +console.log('キューは空か = ', isEmpty); diff --git a/ja/codes/javascript/chapter_stack_and_queue/stack.js b/ja/codes/javascript/chapter_stack_and_queue/stack.js new file mode 100644 index 000000000..79f2af817 --- /dev/null +++ b/ja/codes/javascript/chapter_stack_and_queue/stack.js @@ -0,0 +1,35 @@ +/** + * File: stack.js + * Created Time: 2022-12-04 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* Driver Code */ +/* スタックを初期化 */ +// JavaScript には組み込みのスタッククラスがないため、Array をスタックとして使う +const stack = []; + +/* 要素をプッシュ */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('スタック stack =', stack); + +/* スタックトップの要素にアクセス */ +const peek = stack[stack.length - 1]; +console.log('スタックトップ要素 peek =', peek); + +/* 要素をポップ */ +const pop = stack.pop(); +console.log('ポップした要素 pop =', pop); +console.log('ポップ後 stack =', stack); + +/* スタックの長さを取得 */ +const size = stack.length; +console.log('スタックの長さ size =', size); + +/* 空かどうかを判定 */ +const isEmpty = stack.length === 0; +console.log('スタックは空か =', isEmpty); diff --git a/ja/codes/javascript/chapter_tree/array_binary_tree.js b/ja/codes/javascript/chapter_tree/array_binary_tree.js new file mode 100644 index 000000000..a17cae47d --- /dev/null +++ b/ja/codes/javascript/chapter_tree/array_binary_tree.js @@ -0,0 +1,147 @@ +/** + * File: array_binary_tree.js + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 配列表現による二分木クラス */ +class ArrayBinaryTree { + #tree; + + /* コンストラクタ */ + constructor(arr) { + this.#tree = arr; + } + + /* リスト容量 */ + size() { + return this.#tree.length; + } + + /* インデックス i のノードの値を取得 */ + val(i) { + // インデックスが範囲外なら、空きを表す null を返す + if (i < 0 || i >= this.size()) return null; + return this.#tree[i]; + } + + /* インデックス i のノードの左子ノードのインデックスを取得 */ + left(i) { + return 2 * i + 1; + } + + /* インデックス i のノードの右子ノードのインデックスを取得 */ + right(i) { + return 2 * i + 2; + } + + /* インデックス i のノードの親ノードのインデックスを取得 */ + parent(i) { + return Math.floor((i - 1) / 2); // 切り捨て除算 + } + + /* レベル順走査 */ + levelOrder() { + let res = []; + // 配列を直接走査する + for (let i = 0; i < this.size(); i++) { + if (this.val(i) !== null) res.push(this.val(i)); + } + return res; + } + + /* 深さ優先探索 */ + #dfs(i, order, res) { + // 空きスロットなら返す + if (this.val(i) === null) return; + // 先行順走査 + if (order === 'pre') res.push(this.val(i)); + this.#dfs(this.left(i), order, res); + // 中順走査 + if (order === 'in') res.push(this.val(i)); + this.#dfs(this.right(i), order, res); + // 後順走査 + if (order === 'post') res.push(this.val(i)); + } + + /* 先行順走査 */ + preOrder() { + const res = []; + this.#dfs(0, 'pre', res); + return res; + } + + /* 中順走査 */ + inOrder() { + const res = []; + this.#dfs(0, 'in', res); + return res; + } + + /* 後順走査 */ + postOrder() { + const res = []; + this.#dfs(0, 'post', res); + return res; + } +} + +/* Driver Code */ +// 二分木を初期化 +// ここでは、配列から直接二分木を生成する関数を利用する +const arr = Array.of( + 1, + 2, + 3, + 4, + null, + 6, + 7, + 8, + 9, + null, + null, + 12, + null, + null, + 15 +); + +const root = arrToTree(arr); +console.log('\n二分木を初期化\n'); +console.log('二分木の配列表現:'); +console.log(arr); +console.log('二分木の連結リスト表現:'); +printTree(root); + +// 配列表現による二分木クラス +const abt = new ArrayBinaryTree(arr); + +// ノードにアクセス +const i = 1; +const l = abt.left(i); +const r = abt.right(i); +const p = abt.parent(i); +console.log('\n現在のノードのインデックスは ' + i + ' ,値は ' + abt.val(i)); +console.log( + 'その左子ノードのインデックスは ' + l + ' ,値は ' + (l === null ? 'null' : abt.val(l)) +); +console.log( + 'その右子ノードのインデックスは ' + r + ' ,値は ' + (r === null ? 'null' : abt.val(r)) +); +console.log( + 'その親ノードのインデックスは ' + p + ' ,値は ' + (p === null ? 'null' : abt.val(p)) +); + +// 木を走査 +let res = abt.levelOrder(); +console.log('\nレベル順走査:' + res); +res = abt.preOrder(); +console.log('先行順走査:' + res); +res = abt.inOrder(); +console.log('中間順走査:' + res); +res = abt.postOrder(); +console.log('後行順走査:' + res); diff --git a/ja/codes/javascript/chapter_tree/avl_tree.js b/ja/codes/javascript/chapter_tree/avl_tree.js new file mode 100644 index 000000000..f0e68f4ec --- /dev/null +++ b/ja/codes/javascript/chapter_tree/avl_tree.js @@ -0,0 +1,208 @@ +/** + * File: avl_tree.js + * Created Time: 2023-02-05 + * Author: what-is-me (whatisme@outlook.jp) + */ + +const { TreeNode } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* AVL 木 */ +class AVLTree { + /* コンストラクタ */ + constructor() { + this.root = null; // 根ノード + } + + /* ノードの高さを取得 */ + height(node) { + // 空ノードの高さは -1、葉ノードの高さは 0 + return node === null ? -1 : node.height; + } + + /* ノードの高さを更新する */ + #updateHeight(node) { + // ノードの高さは最も高い部分木の高さ + 1 に等しい + node.height = + Math.max(this.height(node.left), this.height(node.right)) + 1; + } + + /* 平衡係数を取得 */ + balanceFactor(node) { + // 空ノードの平衡係数は 0 + if (node === null) return 0; + // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ + return this.height(node.left) - this.height(node.right); + } + + /* 右回転 */ + #rightRotate(node) { + const child = node.left; + const grandChild = child.right; + // child を支点として node を右回転させる + child.right = node; + node.left = grandChild; + // ノードの高さを更新する + this.#updateHeight(node); + this.#updateHeight(child); + // 回転後の部分木の根ノードを返す + return child; + } + + /* 左回転 */ + #leftRotate(node) { + const child = node.right; + const grandChild = child.left; + // child を支点として node を左回転させる + child.left = node; + node.right = grandChild; + // ノードの高さを更新する + this.#updateHeight(node); + this.#updateHeight(child); + // 回転後の部分木の根ノードを返す + return child; + } + + /* 回転操作を行い、この部分木の平衡を回復する */ + #rotate(node) { + // ノード node の平衡係数を取得 + const balanceFactor = this.balanceFactor(node); + // 左に偏った木 + if (balanceFactor > 1) { + if (this.balanceFactor(node.left) >= 0) { + // 右回転 + return this.#rightRotate(node); + } else { + // 左回転してから右回転 + node.left = this.#leftRotate(node.left); + return this.#rightRotate(node); + } + } + // 右に偏った木 + if (balanceFactor < -1) { + if (this.balanceFactor(node.right) <= 0) { + // 左回転 + return this.#leftRotate(node); + } else { + // 右回転してから左回転 + node.right = this.#rightRotate(node.right); + return this.#leftRotate(node); + } + } + // 平衡木なので回転不要、そのまま返す + return node; + } + + /* ノードを挿入 */ + insert(val) { + this.root = this.#insertHelper(this.root, val); + } + + /* ノードを再帰的に挿入する(補助メソッド) */ + #insertHelper(node, val) { + if (node === null) return new TreeNode(val); + /* 1. 挿入位置を探索してノードを挿入 */ + if (val < node.val) node.left = this.#insertHelper(node.left, val); + else if (val > node.val) + node.right = this.#insertHelper(node.right, val); + else return node; // 重複ノードは挿入せず、そのまま返す + this.#updateHeight(node); // ノードの高さを更新する + /* 2. 回転操作を行い、部分木の平衡を回復する */ + node = this.#rotate(node); + // 部分木の根ノードを返す + return node; + } + + /* ノードを削除 */ + remove(val) { + this.root = this.#removeHelper(this.root, val); + } + + /* ノードを再帰的に削除する(補助メソッド) */ + #removeHelper(node, val) { + if (node === null) return null; + /* 1. ノードを探索して削除 */ + if (val < node.val) node.left = this.#removeHelper(node.left, val); + else if (val > node.val) + node.right = this.#removeHelper(node.right, val); + else { + if (node.left === null || node.right === null) { + const child = node.left !== null ? node.left : node.right; + // 子ノード数 = 0 の場合、node をそのまま削除して返す + if (child === null) return null; + // 子ノード数 = 1 の場合、node をそのまま削除する + else node = child; + } else { + // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える + let temp = node.right; + while (temp.left !== null) { + temp = temp.left; + } + node.right = this.#removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + this.#updateHeight(node); // ノードの高さを更新する + /* 2. 回転操作を行い、部分木の平衡を回復する */ + node = this.#rotate(node); + // 部分木の根ノードを返す + return node; + } + + /* ノードを探索 */ + search(val) { + let cur = this.root; + // ループで探索し、葉ノードを越えたら抜ける + while (cur !== null) { + // 目標ノードは cur の右部分木にある + if (cur.val < val) cur = cur.right; + // 目標ノードは cur の左部分木にある + else if (cur.val > val) cur = cur.left; + // 目標ノードが見つかったらループを抜ける + else break; + } + // 目標ノードを返す + return cur; + } +} + +function testInsert(tree, val) { + tree.insert(val); + console.log('\nノード ' + val + ' を挿入後,AVL 木は'); + printTree(tree.root); +} + +function testRemove(tree, val) { + tree.remove(val); + console.log('\nノード ' + val + ' を削除後,AVL 木は'); + printTree(tree.root); +} + +/* Driver Code */ +/* 空の AVL 木を初期化する */ +const avlTree = new AVLTree(); +/* ノードを挿入 */ +// ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい +testInsert(avlTree, 1); +testInsert(avlTree, 2); +testInsert(avlTree, 3); +testInsert(avlTree, 4); +testInsert(avlTree, 5); +testInsert(avlTree, 8); +testInsert(avlTree, 7); +testInsert(avlTree, 9); +testInsert(avlTree, 10); +testInsert(avlTree, 6); + +/* 重複ノードを挿入する */ +testInsert(avlTree, 7); + +/* ノードを削除 */ +// ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい +testRemove(avlTree, 8); // 次数 0 のノードを削除する +testRemove(avlTree, 5); // 次数 1 のノードを削除する +testRemove(avlTree, 4); // 次数 2 のノードを削除する + +/* ノードを検索 */ +const node = avlTree.search(7); +console.log('\n見つかったノードオブジェクトは', node, ',ノード値 = ' + node.val); diff --git a/ja/codes/javascript/chapter_tree/binary_search_tree.js b/ja/codes/javascript/chapter_tree/binary_search_tree.js new file mode 100644 index 000000000..b10bd1d2a --- /dev/null +++ b/ja/codes/javascript/chapter_tree/binary_search_tree.js @@ -0,0 +1,139 @@ +/** + * File: binary_search_tree.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { TreeNode } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 二分探索木 */ +class BinarySearchTree { + /* コンストラクタ */ + constructor() { + // 空の木を初期化する + this.root = null; + } + + /* 二分木の根ノードを取得 */ + getRoot() { + return this.root; + } + + /* ノードを探索 */ + search(num) { + let cur = this.root; + // ループで探索し、葉ノードを越えたら抜ける + while (cur !== null) { + // 目標ノードは cur の右部分木にある + if (cur.val < num) cur = cur.right; + // 目標ノードは cur の左部分木にある + else if (cur.val > num) cur = cur.left; + // 目標ノードが見つかったらループを抜ける + else break; + } + // 目標ノードを返す + return cur; + } + + /* ノードを挿入 */ + insert(num) { + // 木が空なら、根ノードを初期化する + if (this.root === null) { + this.root = new TreeNode(num); + return; + } + let cur = this.root, + pre = null; + // ループで探索し、葉ノードを越えたら抜ける + while (cur !== null) { + // 重複ノードが見つかったら、直ちに返す + if (cur.val === num) return; + pre = cur; + // 挿入位置は cur の右部分木にある + if (cur.val < num) cur = cur.right; + // 挿入位置は cur の左部分木にある + else cur = cur.left; + } + // ノードを挿入 + const node = new TreeNode(num); + if (pre.val < num) pre.right = node; + else pre.left = node; + } + + /* ノードを削除 */ + remove(num) { + // 木が空なら、そのまま早期リターンする + if (this.root === null) return; + let cur = this.root, + pre = null; + // ループで探索し、葉ノードを越えたら抜ける + while (cur !== null) { + // 削除対象のノードが見つかったら、ループを抜ける + if (cur.val === num) break; + pre = cur; + // 削除対象ノードは cur の右部分木にある + if (cur.val < num) cur = cur.right; + // 削除対象ノードは cur の左部分木にある + else cur = cur.left; + } + // 削除対象ノードがなければそのまま返す + if (cur === null) return; + // 子ノード数 = 0 or 1 + if (cur.left === null || cur.right === null) { + // 子ノード数が 0 / 1 のとき、child = null / その子ノード + const child = cur.left !== null ? cur.left : cur.right; + // ノード cur を削除する + if (cur !== this.root) { + if (pre.left === cur) pre.left = child; + else pre.right = child; + } else { + // 削除ノードが根ノードなら、根ノードを再設定 + this.root = child; + } + } + // 子ノード数 = 2 + else { + // 中順走査における cur の次ノードを取得 + let tmp = cur.right; + while (tmp.left !== null) { + tmp = tmp.left; + } + // ノード tmp を再帰的に削除 + this.remove(tmp.val); + // tmp で cur を上書きする + cur.val = tmp.val; + } + } +} + +/* Driver Code */ +/* 二分探索木を初期化 */ +const bst = new BinarySearchTree(); +// 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる +const nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; +for (const num of nums) { + bst.insert(num); +} +console.log('\n初期化した二分木は\n'); +printTree(bst.getRoot()); + +/* ノードを探索 */ +const node = bst.search(7); +console.log('\n見つかったノードオブジェクトは ' + node + ',ノード値 = ' + node.val); + +/* ノードを挿入 */ +bst.insert(16); +console.log('\nノード 16 を挿入後,二分木は\n'); +printTree(bst.getRoot()); + +/* ノードを削除 */ +bst.remove(1); +console.log('\nノード 1 を削除後,二分木は\n'); +printTree(bst.getRoot()); +bst.remove(2); +console.log('\nノード 2 を削除後,二分木は\n'); +printTree(bst.getRoot()); +bst.remove(4); +console.log('\nノード 4 を削除後,二分木は\n'); +printTree(bst.getRoot()); diff --git a/ja/codes/javascript/chapter_tree/binary_tree.js b/ja/codes/javascript/chapter_tree/binary_tree.js new file mode 100644 index 000000000..15e1c9752 --- /dev/null +++ b/ja/codes/javascript/chapter_tree/binary_tree.js @@ -0,0 +1,35 @@ +/** + * File: binary_tree.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { TreeNode } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* 二分木を初期化 */ +// ノードを初期化 +let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); +// ノード間の参照(ポインタ)を構築する +n1.left = n2; +n1.right = n3; +n2.left = n4; +n2.right = n5; +console.log('\n二分木を初期化\n'); +printTree(n1); + +/* ノードの挿入と削除 */ +const P = new TreeNode(0); +// n1 -> n2 の間にノード P を挿入 +n1.left = P; +P.left = n2; +console.log('\nノード P を挿入後\n'); +printTree(n1); +// ノード P を削除 +n1.left = n2; +console.log('\nノード P を削除後\n'); +printTree(n1); diff --git a/ja/codes/javascript/chapter_tree/binary_tree_bfs.js b/ja/codes/javascript/chapter_tree/binary_tree_bfs.js new file mode 100644 index 000000000..84c5c0d09 --- /dev/null +++ b/ja/codes/javascript/chapter_tree/binary_tree_bfs.js @@ -0,0 +1,34 @@ +/** + * File: binary_tree_bfs.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +/* レベル順走査 */ +function levelOrder(root) { + // キューを初期化し、ルートノードを追加する + const queue = [root]; + // 走査順序を保存するためのリストを初期化する + const list = []; + while (queue.length) { + let node = queue.shift(); // デキュー + list.push(node.val); // ノードの値を保存する + if (node.left) queue.push(node.left); // 左子ノードをキューに追加 + if (node.right) queue.push(node.right); // 右子ノードをキューに追加 + } + return list; +} + +/* Driver Code */ +/* 二分木を初期化 */ +// ここでは、配列から直接二分木を生成する関数を利用する +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); +console.log('\n二分木を初期化\n'); +printTree(root); + +/* レベル順走査 */ +const list = levelOrder(root); +console.log('\nレベル順走査のノード出力シーケンス = ' + list); diff --git a/ja/codes/javascript/chapter_tree/binary_tree_dfs.js b/ja/codes/javascript/chapter_tree/binary_tree_dfs.js new file mode 100644 index 000000000..8dfe5ba16 --- /dev/null +++ b/ja/codes/javascript/chapter_tree/binary_tree_dfs.js @@ -0,0 +1,60 @@ +/** + * File: binary_tree_dfs.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { arrToTree } = require('../modules/TreeNode'); +const { printTree } = require('../modules/PrintUtil'); + +// 走査順序を格納するリストを初期化 +const list = []; + +/* 先行順走査 */ +function preOrder(root) { + if (root === null) return; + // 訪問順序:根ノード -> 左部分木 -> 右部分木 + list.push(root.val); + preOrder(root.left); + preOrder(root.right); +} + +/* 中順走査 */ +function inOrder(root) { + if (root === null) return; + // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 + inOrder(root.left); + list.push(root.val); + inOrder(root.right); +} + +/* 後順走査 */ +function postOrder(root) { + if (root === null) return; + // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード + postOrder(root.left); + postOrder(root.right); + list.push(root.val); +} + +/* Driver Code */ +/* 二分木を初期化 */ +// ここでは、配列から直接二分木を生成する関数を利用する +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); +console.log('\n二分木を初期化\n'); +printTree(root); + +/* 先行順走査 */ +list.length = 0; +preOrder(root); +console.log('\n先行順走査のノード出力シーケンス = ' + list); + +/* 中順走査 */ +list.length = 0; +inOrder(root); +console.log('\n中間順走査のノード出力シーケンス = ' + list); + +/* 後順走査 */ +list.length = 0; +postOrder(root); +console.log('\n後行順走査のノード出力シーケンス = ' + list); diff --git a/ja/codes/javascript/modules/ListNode.js b/ja/codes/javascript/modules/ListNode.js new file mode 100644 index 000000000..accc78efe --- /dev/null +++ b/ja/codes/javascript/modules/ListNode.js @@ -0,0 +1,31 @@ +/** + * File: ListNode.js + * Created Time: 2022-12-12 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 連結リストノード */ +class ListNode { + val; // ノード値 + next; // 次のノードを指す参照(ポインタ) + constructor(val, next) { + this.val = val === undefined ? 0 : val; + this.next = next === undefined ? null : next; + } +} + +/* リストを連結リストにデシリアライズする */ +function arrToLinkedList(arr) { + const dum = new ListNode(0); + let head = dum; + for (const val of arr) { + head.next = new ListNode(val); + head = head.next; + } + return dum.next; +} + +module.exports = { + ListNode, + arrToLinkedList, +}; diff --git a/ja/codes/javascript/modules/PrintUtil.js b/ja/codes/javascript/modules/PrintUtil.js new file mode 100644 index 000000000..46a491eaa --- /dev/null +++ b/ja/codes/javascript/modules/PrintUtil.js @@ -0,0 +1,86 @@ +/** + * File: PrintUtil.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +const { arrToTree } = require('./TreeNode'); + +/* 連結リストを出力 */ +function printLinkedList(head) { + let list = []; + while (head !== null) { + list.push(head.val.toString()); + head = head.next; + } + console.log(list.join(' -> ')); +} + +function Trunk(prev, str) { + this.prev = prev; + this.str = str; +} + +/** + * 二分木を出力 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +function printTree(root) { + printTree(root, null, false); +} + +/* 二分木を出力 */ +function printTree(root, prev, isRight) { + if (root === null) { + return; + } + + let prev_str = ' '; + let trunk = new Trunk(prev, prev_str); + + printTree(root.right, trunk, true); + + if (!prev) { + trunk.str = '———'; + } else if (isRight) { + trunk.str = '/———'; + prev_str = ' |'; + } else { + trunk.str = '\\———'; + prev.str = prev_str; + } + + showTrunks(trunk); + console.log(' ' + root.val); + + if (prev) { + prev.str = prev_str; + } + trunk.str = ' |'; + + printTree(root.left, trunk, false); +} + +function showTrunks(p) { + if (!p) { + return; + } + + showTrunks(p.prev); + process.stdout.write(p.str); +} + +/* ヒープを出力 */ +function printHeap(arr) { + console.log('ヒープの配列表現:'); + console.log(arr); + console.log('ヒープの木構造表現:'); + printTree(arrToTree(arr)); +} + +module.exports = { + printLinkedList, + printTree, + printHeap, +}; diff --git a/ja/codes/javascript/modules/TreeNode.js b/ja/codes/javascript/modules/TreeNode.js new file mode 100644 index 000000000..e7f67abea --- /dev/null +++ b/ja/codes/javascript/modules/TreeNode.js @@ -0,0 +1,35 @@ +/** + * File: TreeNode.js + * Created Time: 2022-12-04 + * Author: IsChristina (christinaxia77@foxmail.com) + */ + +/* 二分木ノード */ +class TreeNode { + val; // ノード値 + left; // 左の子ノードへのポインタ + right; // 右の子ノードへのポインタ + height; // ノードの高さ + constructor(val, left, right, height) { + this.val = val === undefined ? 0 : val; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + this.height = height === undefined ? 0 : height; + } +} + +/* 配列をデシリアライズして二分木に変換する */ +function arrToTree(arr, i = 0) { + if (i < 0 || i >= arr.length || arr[i] === null) { + return null; + } + let root = new TreeNode(arr[i]); + root.left = arrToTree(arr, 2 * i + 1); + root.right = arrToTree(arr, 2 * i + 2); + return root; +} + +module.exports = { + TreeNode, + arrToTree, +}; diff --git a/ja/codes/javascript/modules/Vertex.js b/ja/codes/javascript/modules/Vertex.js new file mode 100644 index 000000000..c7f3d6ac6 --- /dev/null +++ b/ja/codes/javascript/modules/Vertex.js @@ -0,0 +1,35 @@ +/** + * File: Vertex.js + * Created Time: 2023-02-15 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 頂点クラス */ +class Vertex { + val; + constructor(val) { + this.val = val; + } + + /* 値リスト vals を入力し、頂点リスト vets を返す */ + static valsToVets(vals) { + const vets = []; + for (let i = 0; i < vals.length; i++) { + vets[i] = new Vertex(vals[i]); + } + return vets; + } + + /* 頂点リスト vets を入力し、値リスト vals を返す */ + static vetsToVals(vets) { + const vals = []; + for (const vet of vets) { + vals.push(vet.val); + } + return vals; + } +} + +module.exports = { + Vertex, +}; diff --git a/ja/codes/javascript/test_all.js b/ja/codes/javascript/test_all.js new file mode 100644 index 000000000..b43ef49f5 --- /dev/null +++ b/ja/codes/javascript/test_all.js @@ -0,0 +1,63 @@ +import { bold, brightRed } from 'jsr:@std/fmt/colors'; +import { expandGlob } from 'jsr:@std/fs'; +import { relative, resolve } from 'jsr:@std/path'; + +/** + * @typedef {import('jsr:@std/fs').WalkEntry} WalkEntry + * @type {WalkEntry[]} + */ +const entries = []; + +for await (const entry of expandGlob( + resolve(import.meta.dirname, './chapter_*/*.js') +)) { + entries.push(entry); +} + +/** @type {{ status: Promise; stderr: ReadableStream; }[]} */ +const processes = []; + +for (const file of entries) { + const execute = new Deno.Command('node', { + args: [relative(import.meta.dirname, file.path)], + cwd: import.meta.dirname, + stdin: 'piped', + stdout: 'piped', + stderr: 'piped', + }); + + const process = execute.spawn(); + processes.push({ status: process.status, stderr: process.stderr }); +} + +const results = await Promise.all( + processes.map(async (item) => { + const status = await item.status; + return { status, stderr: item.stderr }; + }) +); + +/** @type {ReadableStream[]} */ +const errors = []; + +for (const result of results) { + if (!result.status.success) { + errors.push(result.stderr); + } +} + +console.log(`Tested ${entries.length} files`); +console.log(`Found exception in ${errors.length} files`); + +if (errors.length) { + console.log(); + + for (const error of errors) { + const reader = error.getReader(); + const { value } = await reader.read(); + const decoder = new TextDecoder(); + console.log(`${bold(brightRed('error'))}: ${decoder.decode(value)}`); + } + + throw new Error('Test failed'); +} diff --git a/ja/codes/kotlin/chapter_array_and_linkedlist/array.kt b/ja/codes/kotlin/chapter_array_and_linkedlist/array.kt new file mode 100644 index 000000000..9d6a068df --- /dev/null +++ b/ja/codes/kotlin/chapter_array_and_linkedlist/array.kt @@ -0,0 +1,102 @@ +/** + * File: array.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_array_and_linkedlist + +import java.util.concurrent.ThreadLocalRandom + +/* 要素へランダムアクセス */ +fun randomAccess(nums: IntArray): Int { + // 区間 [0, nums.size) からランダムに数字を 1 つ選ぶ + val randomIndex = ThreadLocalRandom.current().nextInt(0, nums.size) + // ランダムな要素を取得して返す + val randomNum = nums[randomIndex] + return randomNum +} + +/* 配列長を拡張する */ +fun extend(nums: IntArray, enlarge: Int): IntArray { + // 拡張後の長さを持つ配列を初期化する + val res = IntArray(nums.size + enlarge) + // 元の配列の全要素を新しい配列にコピー + for (i in nums.indices) { + res[i] = nums[i] + } + // 拡張後の新しい配列を返す + return res +} + +/* 配列の index 番目に要素 num を挿入 */ +fun insert(nums: IntArray, num: Int, index: Int) { + // インデックス index 以降の全要素を 1 つ後ろへ移動する + for (i in nums.size - 1 downTo index + 1) { + nums[i] = nums[i - 1] + } + // index の要素に num を代入する + nums[index] = num +} + +/* index の要素を削除する */ +fun remove(nums: IntArray, index: Int) { + // インデックス index より後ろの全要素を 1 つ前へ移動する + for (i in index.. P -> n1 + val p = n0.next + val n1 = p?.next + n0.next = n1 +} + +/* 連結リスト内で index 番目のノードにアクセス */ +fun access(head: ListNode?, index: Int): ListNode? { + var h = head + for (i in 0..= size) + throw IndexOutOfBoundsException("インデックスが範囲外") + return arr[index] + } + + /* 要素を更新 */ + fun set(index: Int, num: Int) { + if (index < 0 || index >= size) + throw IndexOutOfBoundsException("インデックスが範囲外") + arr[index] = num + } + + /* 末尾に要素を追加 */ + fun add(num: Int) { + // 要素数が容量を超えると、拡張機構が発動する + if (size == capacity()) + extendCapacity() + arr[size] = num + // 要素数を更新 + size++ + } + + /* 中間に要素を挿入 */ + fun insert(index: Int, num: Int) { + if (index < 0 || index >= size) + throw IndexOutOfBoundsException("インデックスが範囲外") + // 要素数が容量を超えると、拡張機構が発動する + if (size == capacity()) + extendCapacity() + // index 以降の要素をすべて 1 つ後ろへずらす + for (j in size - 1 downTo index) + arr[j + 1] = arr[j] + arr[index] = num + // 要素数を更新 + size++ + } + + /* 要素を削除 */ + fun remove(index: Int): Int { + if (index < 0 || index >= size) + throw IndexOutOfBoundsException("インデックスが範囲外") + val num = arr[index] + // インデックス index より後の要素をすべて 1 つ前に移動する + for (j in index..>, + res: MutableList>?>, + cols: BooleanArray, + diags1: BooleanArray, + diags2: BooleanArray +) { + // すべての行への配置が完了したら、解を記録する + if (row == n) { + val copyState = mutableListOf>() + for (sRow in state) { + copyState.add(sRow.toMutableList()) + } + res.add(copyState) + return + } + // すべての列を走査 + for (col in 0..>?> { + // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す + val state = mutableListOf>() + for (i in 0..() + for (j in 0..>?>() + + backtrack(0, n, state, res, cols, diags1, diags2) + + return res +} + +/* Driver Code */ +fun main() { + val n = 4 + val res = nQueens(n) + + println("入力された盤面の縦横は $n") + println("クイーンの配置パターンは全部で ${res.size} 通り") + for (state in res) { + println("--------------------") + for (row in state!!) { + println(row) + } + } +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_backtracking/permutations_i.kt b/ja/codes/kotlin/chapter_backtracking/permutations_i.kt new file mode 100644 index 000000000..eb47d79e3 --- /dev/null +++ b/ja/codes/kotlin/chapter_backtracking/permutations_i.kt @@ -0,0 +1,53 @@ +/** + * File: permutations_i.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.permutations_i + +/* バックトラッキング:順列 I */ +fun backtrack( + state: MutableList, + choices: IntArray, + selected: BooleanArray, + res: MutableList?> +) { + // 状態の長さが要素数に等しければ、解を記録 + if (state.size == choices.size) { + res.add(state.toMutableList()) + return + } + // すべての選択肢を走査 + for (i in choices.indices) { + val choice = choices[i] + // 枝刈り:要素の重複選択を許可しない + if (!selected[i]) { + // 試行: 選択を行い、状態を更新 + selected[i] = true + state.add(choice) + // 次の選択へ進む + backtrack(state, choices, selected, res) + // バックトラック:選択を取り消し、前の状態に戻す + selected[i] = false + state.removeAt(state.size - 1) + } + } +} + +/* 全順列 I */ +fun permutationsI(nums: IntArray): MutableList?> { + val res = mutableListOf?>() + backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(1, 2, 3) + + val res = permutationsI(nums) + + println("入力配列 nums = ${nums.contentToString()}") + println("すべての順列 res = $res") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_backtracking/permutations_ii.kt b/ja/codes/kotlin/chapter_backtracking/permutations_ii.kt new file mode 100644 index 000000000..6869a3b94 --- /dev/null +++ b/ja/codes/kotlin/chapter_backtracking/permutations_ii.kt @@ -0,0 +1,54 @@ +/** + * File: permutations_ii.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.permutations_ii + +/* バックトラッキング:順列 II */ +fun backtrack( + state: MutableList, + choices: IntArray, + selected: BooleanArray, + res: MutableList?> +) { + // 状態の長さが要素数に等しければ、解を記録 + if (state.size == choices.size) { + res.add(state.toMutableList()) + return + } + // すべての選択肢を走査 + val duplicated = HashSet() + for (i in choices.indices) { + val choice = choices[i] + // 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない + if (!selected[i] && !duplicated.contains(choice)) { + // 試行: 選択を行い、状態を更新 + duplicated.add(choice) // 選択済みの要素値を記録 + selected[i] = true + state.add(choice) + // 次の選択へ進む + backtrack(state, choices, selected, res) + // バックトラック:選択を取り消し、前の状態に戻す + selected[i] = false + state.removeAt(state.size - 1) + } + } +} + +/* 全順列 II */ +fun permutationsII(nums: IntArray): MutableList?> { + val res = mutableListOf?>() + backtrack(mutableListOf(), nums, BooleanArray(nums.size), res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(1, 2, 2) + val res = permutationsII(nums) + + println("入力配列 nums = ${nums.contentToString()}") + println("すべての順列 res = $res") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt b/ja/codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt new file mode 100644 index 000000000..304ab4d97 --- /dev/null +++ b/ja/codes/kotlin/chapter_backtracking/preorder_traversal_i_compact.kt @@ -0,0 +1,43 @@ +/** + * File: preorder_traversal_i_compact.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.preorder_traversal_i_compact + +import utils.TreeNode +import utils.printTree + +var res: MutableList? = null + +/* 前順走査:例題 1 */ +fun preOrder(root: TreeNode?) { + if (root == null) { + return + } + if (root._val == 7) { + // 解を記録 + res!!.add(root) + } + preOrder(root.left) + preOrder(root.right) +} + +/* Driver Code */ +fun main() { + val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) + println("\n二分木を初期化") + printTree(root) + + // 先行順走査 + res = mutableListOf() + preOrder(root) + + println("\n値が 7 のノードをすべて出力") + val vals = mutableListOf() + for (node in res!!) { + vals.add(node._val) + } + println(vals) +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt b/ja/codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt new file mode 100644 index 000000000..a8edc4333 --- /dev/null +++ b/ja/codes/kotlin/chapter_backtracking/preorder_traversal_ii_compact.kt @@ -0,0 +1,51 @@ +/** + * File: preorder_traversal_ii_compact.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.preorder_traversal_ii_compact + +import utils.TreeNode +import utils.printTree + +var path: MutableList? = null +var res: MutableList>? = null + +/* 前順走査:例題 2 */ +fun preOrder(root: TreeNode?) { + if (root == null) { + return + } + // 試す + path!!.add(root) + if (root._val == 7) { + // 解を記録 + res!!.add(path!!.toMutableList()) + } + preOrder(root.left) + preOrder(root.right) + // バックトラック + path!!.removeAt(path!!.size - 1) +} + +/* Driver Code */ +fun main() { + val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) + println("\n二分木を初期化") + printTree(root) + + // 先行順走査 + path = mutableListOf() + res = mutableListOf() + preOrder(root) + + println("\n根ノードからノード 7 までのすべての経路を出力") + for (path in res!!) { + val _vals = mutableListOf() + for (node in path) { + _vals.add(node._val) + } + println(_vals) + } +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt b/ja/codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt new file mode 100644 index 000000000..2cbb153d2 --- /dev/null +++ b/ja/codes/kotlin/chapter_backtracking/preorder_traversal_iii_compact.kt @@ -0,0 +1,52 @@ +/** + * File: preorder_traversal_iii_compact.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.preorder_traversal_iii_compact + +import utils.TreeNode +import utils.printTree + +var path: MutableList? = null +var res: MutableList>? = null + +/* 前順走査:例題 3 */ +fun preOrder(root: TreeNode?) { + // 枝刈り + if (root == null || root._val == 3) { + return + } + // 試す + path!!.add(root) + if (root._val == 7) { + // 解を記録 + res!!.add(path!!.toMutableList()) + } + preOrder(root.left) + preOrder(root.right) + // バックトラック + path!!.removeAt(path!!.size - 1) +} + +/* Driver Code */ +fun main() { + val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) + println("\n二分木を初期化") + printTree(root) + + // 先行順走査 + path = mutableListOf() + res = mutableListOf() + preOrder(root) + + println("\n根ノードからノード 7 までのすべての経路を出力し,経路には値が 3 のノードを含めない") + for (path in res!!) { + val _vals = mutableListOf() + for (node in path) { + _vals.add(node._val) + } + println(_vals) + } +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt b/ja/codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt new file mode 100644 index 000000000..f6f7e9923 --- /dev/null +++ b/ja/codes/kotlin/chapter_backtracking/preorder_traversal_iii_template.kt @@ -0,0 +1,82 @@ +/** + * File: preorder_traversal_iii_template.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.preorder_traversal_iii_template + +import utils.TreeNode +import utils.printTree + +/* 現在の状態が解かどうかを判定 */ +fun isSolution(state: MutableList): Boolean { + return state.isNotEmpty() && state[state.size - 1]?._val == 7 +} + +/* 解を記録 */ +fun recordSolution(state: MutableList?, res: MutableList?>) { + res.add(state!!.toMutableList()) +} + +/* 現在の状態で、この選択が有効かどうかを判定 */ +fun isValid(state: MutableList?, choice: TreeNode?): Boolean { + return choice != null && choice._val != 3 +} + +/* 状態を更新 */ +fun makeChoice(state: MutableList, choice: TreeNode?) { + state.add(choice) +} + +/* 状態を元に戻す */ +fun undoChoice(state: MutableList, choice: TreeNode?) { + state.removeLast() +} + +/* バックトラッキング:例題 3 */ +fun backtrack( + state: MutableList, + choices: MutableList, + res: MutableList?> +) { + // 解かどうかを確認 + if (isSolution(state)) { + // 解を記録 + recordSolution(state, res) + } + // すべての選択肢を走査 + for (choice in choices) { + // 枝刈り:選択が妥当かを確認する + if (isValid(state, choice)) { + // 試行: 選択を行い、状態を更新 + makeChoice(state, choice) + // 次の選択へ進む + backtrack(state, mutableListOf(choice!!.left, choice.right), res) + // バックトラック:選択を取り消し、前の状態に戻す + undoChoice(state, choice) + } + } +} + +/* Driver Code */ +fun main() { + val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7)) + println("\n二分木を初期化") + printTree(root) + + // バックトラッキング法 + val res = mutableListOf?>() + backtrack(mutableListOf(), mutableListOf(root), res) + + println("\n根ノードからノード 7 までのすべての経路を出力し,経路に値が 3 のノードを含まないことを条件とする") + for (path in res) { + val vals = mutableListOf() + for (node in path!!) { + if (node != null) { + vals.add(node._val) + } + } + println(vals) + } +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_backtracking/subset_sum_i.kt b/ja/codes/kotlin/chapter_backtracking/subset_sum_i.kt new file mode 100644 index 000000000..d2c803b25 --- /dev/null +++ b/ja/codes/kotlin/chapter_backtracking/subset_sum_i.kt @@ -0,0 +1,58 @@ +/** + * File: subset_sum_i.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.subset_sum_i + +/* バックトラッキング:部分和 I */ +fun backtrack( + state: MutableList, + target: Int, + choices: IntArray, + start: Int, + res: MutableList?> +) { + // 部分集合の和が target に等しければ、解を記録 + if (target == 0) { + res.add(state.toMutableList()) + return + } + // すべての選択肢を走査 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + for (i in start..?> { + val state = mutableListOf() // 状態(部分集合) + nums.sort() // nums をソート + val start = 0 // 開始点を走査 + val res = mutableListOf?>() // 結果リスト(部分集合のリスト) + backtrack(state, target, nums, start, res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(3, 4, 5) + val target = 9 + + val res = subsetSumI(nums, target) + + println("入力配列 nums = ${nums.contentToString()}, target = $target") + println("和が $target に等しいすべての部分集合 res = $res") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt b/ja/codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt new file mode 100644 index 000000000..a5d86ecc9 --- /dev/null +++ b/ja/codes/kotlin/chapter_backtracking/subset_sum_i_naive.kt @@ -0,0 +1,55 @@ +/** + * File: subset_sum_i_native.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.subset_sum_i_naive + +/* バックトラッキング:部分和 I */ +fun backtrack( + state: MutableList, + target: Int, + total: Int, + choices: IntArray, + res: MutableList?> +) { + // 部分集合の和が target に等しければ、解を記録 + if (total == target) { + res.add(state.toMutableList()) + return + } + // すべての選択肢を走査 + for (i in choices.indices) { + // 枝刈り:部分和が target を超える場合はその選択をスキップする + if (total + choices[i] > target) { + continue + } + // 試行:選択を行い、要素と total を更新する + state.add(choices[i]) + // 次の選択へ進む + backtrack(state, target, total + choices[i], choices, res) + // バックトラック:選択を取り消し、前の状態に戻す + state.removeAt(state.size - 1) + } +} + +/* 部分和 I を解く(重複部分集合を含む) */ +fun subsetSumINaive(nums: IntArray, target: Int): MutableList?> { + val state = mutableListOf() // 状態(部分集合) + val total = 0 // 部分和 + val res = mutableListOf?>() // 結果リスト(部分集合のリスト) + backtrack(state, target, total, nums, res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(3, 4, 5) + val target = 9 + val res = subsetSumINaive(nums, target) + + println("入力配列 nums = ${nums.contentToString()}, target = $target") + println("和が $target に等しいすべての部分集合 res = $res") + println("この方法の出力結果には重複した集合が含まれるので注意") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_backtracking/subset_sum_ii.kt b/ja/codes/kotlin/chapter_backtracking/subset_sum_ii.kt new file mode 100644 index 000000000..6967e2385 --- /dev/null +++ b/ja/codes/kotlin/chapter_backtracking/subset_sum_ii.kt @@ -0,0 +1,62 @@ +/** + * File: subset_sum_ii.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_backtracking.subset_sum_ii + +/* バックトラッキング:部分和 II */ +fun backtrack( + state: MutableList, + target: Int, + choices: IntArray, + start: Int, + res: MutableList?> +) { + // 部分集合の和が target に等しければ、解を記録 + if (target == 0) { + res.add(state.toMutableList()) + return + } + // すべての選択肢を走査 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける + for (i in start.. start && choices[i] == choices[i - 1]) { + continue + } + // 試す:選択を行い、target と start を更新 + state.add(choices[i]) + // 次の選択へ進む + backtrack(state, target - choices[i], choices, i + 1, res) + // バックトラック:選択を取り消し、前の状態に戻す + state.removeAt(state.size - 1) + } +} + +/* 部分和 II を解く */ +fun subsetSumII(nums: IntArray, target: Int): MutableList?> { + val state = mutableListOf() // 状態(部分集合) + nums.sort() // nums をソート + val start = 0 // 開始点を走査 + val res = mutableListOf?>() // 結果リスト(部分集合のリスト) + backtrack(state, target, nums, start, res) + return res +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(4, 4, 5) + val target = 9 + val res = subsetSumII(nums, target) + + println("入力配列 nums = ${nums.contentToString()}, target = $target") + println("和が $target に等しいすべての部分集合 res = $res") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_computational_complexity/iteration.kt b/ja/codes/kotlin/chapter_computational_complexity/iteration.kt new file mode 100644 index 000000000..32f9a1bb2 --- /dev/null +++ b/ja/codes/kotlin/chapter_computational_complexity/iteration.kt @@ -0,0 +1,74 @@ +/** + * File: iteration.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_computational_complexity.iteration + +/* for ループ */ +fun forLoop(n: Int): Int { + var res = 0 + // 1, 2, ..., n-1, n を順に加算する + for (i in 1..n) { + res += i + } + return res +} + +/* while ループ */ +fun whileLoop(n: Int): Int { + var res = 0 + var i = 1 // 条件変数を初期化する + // 1, 2, ..., n-1, n を順に加算する + while (i <= n) { + res += i + i++ // 条件変数を更新する + } + return res +} + +/* while ループ(2回更新) */ +fun whileLoopII(n: Int): Int { + var res = 0 + var i = 1 // 条件変数を初期化する + // 1, 4, 10, ... を順に加算する + while (i <= n) { + res += i + // 条件変数を更新する + i++ + i *= 2 + } + return res +} + +/* 二重 for ループ */ +fun nestedForLoop(n: Int): String { + val res = StringBuilder() + // i = 1, 2, ..., n-1, n とループする + for (i in 1..n) { + // j = 1, 2, ..., n-1, n とループする + for (j in 1..n) { + res.append(" ($i, $j), ") + } + } + return res.toString() +} + +/* Driver Code */ +fun main() { + val n = 5 + var res: Int + + res = forLoop(n) + println("\nfor ループの合計結果 res = $res") + + res = whileLoop(n) + println("\nwhile ループの合計結果 res = $res") + + res = whileLoopII(n) + println("\nwhile ループ (2 回更新) の合計結果 res = $res") + + val resStr = nestedForLoop(n) + println("\n二重 for ループの走査結果 $resStr") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_computational_complexity/recursion.kt b/ja/codes/kotlin/chapter_computational_complexity/recursion.kt new file mode 100644 index 000000000..a4be116c6 --- /dev/null +++ b/ja/codes/kotlin/chapter_computational_complexity/recursion.kt @@ -0,0 +1,78 @@ +/** + * File: recursion.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_computational_complexity.recursion + +import java.util.* + +/* 再帰 */ +fun recur(n: Int): Int { + // 終了条件 + if (n == 1) + return 1 + // 再帰: 再帰呼び出し + val res = recur(n - 1) + // 戻る: 結果を返す + return n + res +} + +/* 反復で再帰を模擬する */ +fun forLoopRecur(n: Int): Int { + // 明示的なスタックを使ってシステムコールスタックを模擬する + val stack = Stack() + var res = 0 + // 再帰: 再帰呼び出し + for (i in n downTo 0) { + // 「スタックへのプッシュ」で「再帰」を模擬する + stack.push(i) + } + // 戻る: 結果を返す + while (stack.isNotEmpty()) { + // 「スタックから取り出す操作」で「帰り」をシミュレート + res += stack.pop() + } + // res = 1+2+3+...+n + return res +} + +/* 末尾再帰 */ +tailrec fun tailRecur(n: Int, res: Int): Int { + // `tailrec` キーワードを追加して末尾再帰最適化を有効にする + // 終了条件 + if (n == 0) + return res + // 末尾再帰呼び出し + return tailRecur(n - 1, res + n) +} + +/* フィボナッチ数列:再帰 */ +fun fib(n: Int): Int { + // 終了条件 f(1) = 0, f(2) = 1 + if (n == 1 || n == 2) + return n - 1 + // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す + val res = fib(n - 1) + fib(n - 2) + // 結果 f(n) を返す + return res +} + +/* Driver Code */ +fun main() { + val n = 5 + var res: Int + + res = recur(n) + println("\n再帰関数の合計結果 res = $res") + + res = forLoopRecur(n) + println("\n反復で再帰をシミュレートした合計結果 res = $res") + + res = tailRecur(n, 0) + println("\n末尾再帰関数の合計結果 res = $res") + + res = fib(n) + println("\nフィボナッチ数列の第 $n 項は $res") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_computational_complexity/space_complexity.kt b/ja/codes/kotlin/chapter_computational_complexity/space_complexity.kt new file mode 100644 index 000000000..0f3e2f1e0 --- /dev/null +++ b/ja/codes/kotlin/chapter_computational_complexity/space_complexity.kt @@ -0,0 +1,109 @@ +/** + * File: space_complexity.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_computational_complexity.space_complexity + +import utils.ListNode +import utils.TreeNode +import utils.printTree + +/* 関数 */ +fun function(): Int { + // 何らかの処理を行う + return 0 +} + +/* 定数階 */ +fun constant(n: Int) { + // 定数、変数、オブジェクトは O(1) の空間を占める + val a = 0 + var b = 0 + val nums = Array(10000) { 0 } + val node = ListNode(0) + // ループ内の変数は O(1) の空間を占める + for (i in 0..() + for (i in 0..() + for (i in 0..?>(n) + // 二次元リストは O(n^2) の空間を使用 + val numList = mutableListOf>() + for (i in 0..() + for (j in 0.. nums[j + 1]) { + // nums[j] と nums[j + 1] を交換 + val temp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = temp + count += 3 // 要素交換には 3 回の単位操作が含まれる + } + } + } + return count +} + +/* 指数時間(ループ実装) */ +fun exponential(n: Int): Int { + var count = 0 + var base = 1 + // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する + for (i in 0.. 1) { + n1 /= 2 + count++ + } + return count +} + +/* 対数時間(再帰実装) */ +fun logRecur(n: Int): Int { + if (n <= 1) + return 0 + return logRecur(n / 2) + 1 +} + +/* 線形対数時間 */ +fun linearLogRecur(n: Int): Int { + if (n <= 1) + return 1 + var count = linearLogRecur(n / 2) + linearLogRecur(n / 2) + for (i in 0.. { + val nums = IntArray(n) + // 配列 nums = { 1, 2, 3, ..., n } を生成 + for (i in 0..(n) + for (i in 0..): Int { + for (i in nums.indices) { + // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる + // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる + if (nums[i] == 1) + return i + } + return -1 +} + +/* Driver Code */ +fun main() { + for (i in 0..9) { + val n = 100 + val nums = randomNumbers(n) + val index = findOne(nums) + println("\n配列 [ 1, 2, ..., n ] をシャッフルした後 = ${nums.contentToString()}") + println("数字 1 のインデックスは $index") + } +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt b/ja/codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt new file mode 100644 index 000000000..2ccab1248 --- /dev/null +++ b/ja/codes/kotlin/chapter_divide_and_conquer/binary_search_recur.kt @@ -0,0 +1,49 @@ +/** + * File: binary_search_recur.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_divide_and_conquer.binary_search_recur + +/* 二分探索:問題 f(i, j) */ +fun dfs( + nums: IntArray, + target: Int, + i: Int, + j: Int +): Int { + // 区間が空なら対象要素は存在しないので -1 を返す + if (i > j) { + return -1 + } + // 中点インデックス m を計算 + val m = (i + j) / 2 + return if (nums[m] < target) { + // 部分問題 f(m+1, j) を再帰的に解く + dfs(nums, target, m + 1, j) + } else if (nums[m] > target) { + // 部分問題 f(i, m-1) を再帰的に解く + dfs(nums, target, i, m - 1) + } else { + // 目標要素が見つかったらそのインデックスを返す + m + } +} + +/* 二分探索 */ +fun binarySearch(nums: IntArray, target: Int): Int { + val n = nums.size + // 問題 f(0, n-1) を解く + return dfs(nums, target, 0, n - 1) +} + +/* Driver Code */ +fun main() { + val target = 6 + val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) + + // 二分探索(両閉区間) + val index = binarySearch(nums, target) + println("対象要素 6 のインデックス = $index") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_divide_and_conquer/build_tree.kt b/ja/codes/kotlin/chapter_divide_and_conquer/build_tree.kt new file mode 100644 index 000000000..43ddadaaf --- /dev/null +++ b/ja/codes/kotlin/chapter_divide_and_conquer/build_tree.kt @@ -0,0 +1,55 @@ +/** + * File: build_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_divide_and_conquer.build_tree + +import utils.TreeNode +import utils.printTree + +/* 二分木を構築:分割統治 */ +fun dfs( + preorder: IntArray, + inorderMap: Map, + i: Int, + l: Int, + r: Int +): TreeNode? { + // 部分木区間が空なら終了する + if (r - l < 0) return null + // ルートノードを初期化する + val root = TreeNode(preorder[i]) + // m を求めて左右部分木を分割する + val m = inorderMap[preorder[i]]!! + // 部分問題:左部分木を構築する + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1) + // 部分問題:右部分木を構築する + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r) + // 根ノードを返す + return root +} + +/* 二分木を構築 */ +fun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? { + // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する + val inorderMap = HashMap() + for (i in inorder.indices) { + inorderMap[inorder[i]] = i + } + val root = dfs(preorder, inorderMap, 0, 0, inorder.size - 1) + return root +} + +/* Driver Code */ +fun main() { + val preorder = intArrayOf(3, 9, 2, 1, 7) + val inorder = intArrayOf(9, 3, 1, 2, 7) + println("前順走査 = ${preorder.contentToString()}") + println("中順走査 = ${inorder.contentToString()}") + + val root = buildTree(preorder, inorder) + println("構築した二分木:") + printTree(root) +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_divide_and_conquer/hanota.kt b/ja/codes/kotlin/chapter_divide_and_conquer/hanota.kt new file mode 100644 index 000000000..4acd8704e --- /dev/null +++ b/ja/codes/kotlin/chapter_divide_and_conquer/hanota.kt @@ -0,0 +1,56 @@ +/** + * File: hanota.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_divide_and_conquer.hanota + +/* 円盤を 1 枚移動 */ +fun move(src: MutableList, tar: MutableList) { + // src の上から円盤を1枚取り出す + val pan = src.removeAt(src.size - 1) + // 円盤を tar の上に置く + tar.add(pan) +} + +/* ハノイの塔の問題 f(i) を解く */ +fun dfs(i: Int, src: MutableList, buf: MutableList, tar: MutableList) { + // src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す + if (i == 1) { + move(src, tar) + return + } + // 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す + dfs(i - 1, src, tar, buf) + // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す + move(src, tar) + // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す + dfs(i - 1, buf, src, tar) +} + +/* ハノイの塔を解く */ +fun solveHanota(A: MutableList, B: MutableList, C: MutableList) { + val n = A.size + // A の上から n 枚の円盤を B を介して C へ移す + dfs(n, A, B, C) +} + +/* Driver Code */ +fun main() { + // リスト末尾が柱の頂上 + val A = mutableListOf(5, 4, 3, 2, 1) + val B = mutableListOf() + val C = mutableListOf() + println("初期状態:") + println("A = $A") + println("B = $B") + println("C = $C") + + solveHanota(A, B, C) + + println("円盤の移動完了後:") + println("A = $A") + println("B = $B") + println("C = $C") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt b/ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt new file mode 100644 index 000000000..e3a4eeda2 --- /dev/null +++ b/ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_backtrack.kt @@ -0,0 +1,45 @@ +/** + * File: climbing_stairs_backtrack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* バックトラッキング */ +fun backtrack( + choices: MutableList, + state: Int, + n: Int, + res: MutableList +) { + // 第 n 段に到達したら、方法数を 1 増やす + if (state == n) + res[0] = res[0] + 1 + // すべての選択肢を走査 + for (choice in choices) { + // 枝刈り: 第 n 段を超えないようにする + if (state + choice > n) continue + // 試行: 選択を行い、状態を更新 + backtrack(choices, state + choice, n, res) + // バックトラック + } +} + +/* 階段登り:バックトラッキング */ +fun climbingStairsBacktrack(n: Int): Int { + val choices = mutableListOf(1, 2) // 1 段または 2 段上ることを選べる + val state = 0 // 第 0 段から上り始める + val res = mutableListOf() + res.add(0) // res[0] を使って方法数を記録する + backtrack(choices, state, n, res) + return res[0] +} + +/* Driver Code */ +fun main() { + val n = 9 + + val res = climbingStairsBacktrack(n) + println("$n 段の階段の登り方は全部で $res 通り") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt b/ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt new file mode 100644 index 000000000..70bd4999f --- /dev/null +++ b/ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_constraint_dp.kt @@ -0,0 +1,35 @@ +/** + * File: climbing_stairs_constraint_dp.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* 制約付き階段登り:動的計画法 */ +fun climbingStairsConstraintDP(n: Int): Int { + if (n == 1 || n == 2) { + return 1 + } + // 部分問題の解を保存するために dp テーブルを初期化 + val dp = Array(n + 1) { IntArray(3) } + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1][1] = 1 + dp[1][2] = 0 + dp[2][1] = 0 + dp[2][2] = 1 + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for (i in 3..n) { + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + } + return dp[n][1] + dp[n][2] +} + +/* Driver Code */ +fun main() { + val n = 9 + + val res = climbingStairsConstraintDP(n) + println("$n 段の階段の登り方は全部で $res 通り") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt b/ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt new file mode 100644 index 000000000..a5200a91e --- /dev/null +++ b/ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs.kt @@ -0,0 +1,29 @@ +/** + * File: climbing_stairs_dfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* 検索 */ +fun dfs(i: Int): Int { + // dp[1] と dp[2] は既知なので返す + if (i == 1 || i == 2) return i + // dp[i] = dp[i-1] + dp[i-2] + val count = dfs(i - 1) + dfs(i - 2) + return count +} + +/* 階段登り:探索 */ +fun climbingStairsDFS(n: Int): Int { + return dfs(n) +} + +/* Driver Code */ +fun main() { + val n = 9 + + val res = climbingStairsDFS(n) + println("$n 段の階段の登り方は全部で $res 通り") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt b/ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt new file mode 100644 index 000000000..3898dd81a --- /dev/null +++ b/ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dfs_mem.kt @@ -0,0 +1,36 @@ +/** + * File: climbing_stairs_dfs_mem.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* メモ化探索 */ +fun dfs(i: Int, mem: IntArray): Int { + // dp[1] と dp[2] は既知なので返す + if (i == 1 || i == 2) return i + // dp[i] の記録があれば、それをそのまま返す + if (mem[i] != -1) return mem[i] + // dp[i] = dp[i-1] + dp[i-2] + val count = dfs(i - 1, mem) + dfs(i - 2, mem) + // dp[i] を記録する + mem[i] = count + return count +} + +/* 階段登り:メモ化探索 */ +fun climbingStairsDFSMem(n: Int): Int { + // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す + val mem = IntArray(n + 1) + mem.fill(-1) + return dfs(n, mem) +} + +/* Driver Code */ +fun main() { + val n = 9 + + val res = climbingStairsDFSMem(n) + println("$n 段の階段の登り方は全部で $res 通り") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt b/ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt new file mode 100644 index 000000000..4f2d39e58 --- /dev/null +++ b/ja/codes/kotlin/chapter_dynamic_programming/climbing_stairs_dp.kt @@ -0,0 +1,46 @@ +/** + * File: climbing_stairs_dp.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* 階段登り:動的計画法 */ +fun climbingStairsDP(n: Int): Int { + if (n == 1 || n == 2) return n + // 部分問題の解を保存するために dp テーブルを初期化 + val dp = IntArray(n + 1) + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1] = 1 + dp[2] = 2 + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for (i in 3..n) { + dp[i] = dp[i - 1] + dp[i - 2] + } + return dp[n] +} + +/* 階段登り:空間最適化した動的計画法 */ +fun climbingStairsDPComp(n: Int): Int { + if (n == 1 || n == 2) return n + var a = 1 + var b = 2 + for (i in 3..n) { + val temp = b + b += a + a = temp + } + return b +} + +/* Driver Code */ +fun main() { + val n = 9 + + var res = climbingStairsDP(n) + println("$n 段の階段の登り方は全部で $res 通り") + + res = climbingStairsDPComp(n) + println("$n 段の階段の登り方は全部で $res 通り") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_dynamic_programming/coin_change.kt b/ja/codes/kotlin/chapter_dynamic_programming/coin_change.kt new file mode 100644 index 000000000..aa0517b1a --- /dev/null +++ b/ja/codes/kotlin/chapter_dynamic_programming/coin_change.kt @@ -0,0 +1,71 @@ +/** + * File: coin_change.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.min + +/* コイン両替:動的計画法 */ +fun coinChangeDP(coins: IntArray, amt: Int): Int { + val n = coins.size + val MAX = amt + 1 + // dp テーブルを初期化 + val dp = Array(n + 1) { IntArray(amt + 1) } + // 状態遷移:先頭行と先頭列 + for (a in 1..amt) { + dp[0][a] = MAX + } + // 状態遷移: 残りの行と列 + for (i in 1..n) { + for (a in 1..amt) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[i][a] = dp[i - 1][a] + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) + } + } + } + return if (dp[n][amt] != MAX) dp[n][amt] else -1 +} + +/* コイン交換:空間最適化後の動的計画法 */ +fun coinChangeDPComp(coins: IntArray, amt: Int): Int { + val n = coins.size + val MAX = amt + 1 + // dp テーブルを初期化 + val dp = IntArray(amt + 1) + dp.fill(MAX) + dp[0] = 0 + // 状態遷移 + for (i in 1..n) { + for (a in 1..amt) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a] + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) + } + } + } + return if (dp[amt] != MAX) dp[amt] else -1 +} + +/* Driver Code */ +fun main() { + val coins = intArrayOf(1, 2, 5) + val amt = 4 + + // 動的計画法 + var res = coinChangeDP(coins, amt) + println("目標金額を作るのに必要な最小硬貨枚数は $res") + + // 空間最適化後の動的計画法 + res = coinChangeDPComp(coins, amt) + println("目標金額を作るのに必要な最小硬貨枚数は $res") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt b/ja/codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt new file mode 100644 index 000000000..10f9d8f3c --- /dev/null +++ b/ja/codes/kotlin/chapter_dynamic_programming/coin_change_ii.kt @@ -0,0 +1,66 @@ +/** + * File: coin_change_ii.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +/* コイン両替 II:動的計画法 */ +fun coinChangeIIDP(coins: IntArray, amt: Int): Int { + val n = coins.size + // dp テーブルを初期化 + val dp = Array(n + 1) { IntArray(amt + 1) } + // 先頭列を初期化する + for (i in 0..n) { + dp[i][0] = 1 + } + // 状態遷移 + for (i in 1..n) { + for (a in 1..amt) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[i][a] = dp[i - 1][a] + } else { + // コイン i を選ばない場合と選ぶ場合の和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] + } + } + } + return dp[n][amt] +} + +/* コイン両替 II:空間最適化した動的計画法 */ +fun coinChangeIIDPComp(coins: IntArray, amt: Int): Int { + val n = coins.size + // dp テーブルを初期化 + val dp = IntArray(amt + 1) + dp[0] = 1 + // 状態遷移 + for (i in 1..n) { + for (a in 1..amt) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a] + } else { + // コイン i を選ばない場合と選ぶ場合の和 + dp[a] = dp[a] + dp[a - coins[i - 1]] + } + } + } + return dp[amt] +} + +/* Driver Code */ +fun main() { + val coins = intArrayOf(1, 2, 5) + val amt = 5 + + // 動的計画法 + var res = coinChangeIIDP(coins, amt) + println("目標金額を作る硬貨の組み合わせ数は $res") + + // 空間最適化後の動的計画法 + res = coinChangeIIDPComp(coins, amt) + println("目標金額を作る硬貨の組み合わせ数は $res") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_dynamic_programming/edit_distance.kt b/ja/codes/kotlin/chapter_dynamic_programming/edit_distance.kt new file mode 100644 index 000000000..0a6b3ae24 --- /dev/null +++ b/ja/codes/kotlin/chapter_dynamic_programming/edit_distance.kt @@ -0,0 +1,143 @@ +/** + * File: edit_distance.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.min + +/* 編集距離:総当たり探索 */ +fun editDistanceDFS( + s: String, + t: String, + i: Int, + j: Int +): Int { + // s と t がともに空なら 0 を返す + if (i == 0 && j == 0) return 0 + // s が空なら t の長さを返す + if (i == 0) return j + // t が空なら s の長さを返す + if (j == 0) return i + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1) + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + val insert = editDistanceDFS(s, t, i, j - 1) + val delete = editDistanceDFS(s, t, i - 1, j) + val replace = editDistanceDFS(s, t, i - 1, j - 1) + // 最小編集回数を返す + return min(min(insert, delete), replace) + 1 +} + +/* 編集距離:メモ化探索 */ +fun editDistanceDFSMem( + s: String, + t: String, + mem: Array, + i: Int, + j: Int +): Int { + // s と t がともに空なら 0 を返す + if (i == 0 && j == 0) return 0 + // s が空なら t の長さを返す + if (i == 0) return j + // t が空なら s の長さを返す + if (j == 0) return i + // 記録済みなら、それをそのまま返す + if (mem[i][j] != -1) return mem[i][j] + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1) + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + val insert = editDistanceDFSMem(s, t, mem, i, j - 1) + val delete = editDistanceDFSMem(s, t, mem, i - 1, j) + val replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1) + // 最小編集回数を記録して返す + mem[i][j] = min(min(insert, delete), replace) + 1 + return mem[i][j] +} + +/* 編集距離:動的計画法 */ +fun editDistanceDP(s: String, t: String): Int { + val n = s.length + val m = t.length + val dp = Array(n + 1) { IntArray(m + 1) } + // 状態遷移:先頭行と先頭列 + for (i in 1..n) { + dp[i][0] = i + } + for (j in 1..m) { + dp[0][j] = j + } + // 状態遷移: 残りの行と列 + for (i in 1..n) { + for (j in 1..m) { + if (s[i - 1] == t[j - 1]) { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[i][j] = dp[i - 1][j - 1] + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 + } + } + } + return dp[n][m] +} + +/* 編集距離:空間最適化した動的計画法 */ +fun editDistanceDPComp(s: String, t: String): Int { + val n = s.length + val m = t.length + val dp = IntArray(m + 1) + // 状態遷移:先頭行 + for (j in 1..m) { + dp[j] = j + } + // 状態遷移:残りの行 + for (i in 1..n) { + // 状態遷移:先頭列 + var leftup = dp[0] // dp[i-1, j-1] を一時保存する + dp[0] = i + // 状態遷移:残りの列 + for (j in 1..m) { + val temp = dp[j] + if (s[i - 1] == t[j - 1]) { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[j] = leftup + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 + } + leftup = temp // 次の反復の dp[i-1, j-1] に更新する + } + } + return dp[m] +} + +/* Driver Code */ +fun main() { + val s = "bag" + val t = "pack" + val n = s.length + val m = t.length + + // 全探索 + var res = editDistanceDFS(s, t, n, m) + println("$s を $t に変更するには最小で $res 回の編集が必要") + + // メモ化探索 + val mem = Array(n + 1) { IntArray(m + 1) } + for (row in mem) + row.fill(-1) + res = editDistanceDFSMem(s, t, mem, n, m) + println("$s を $t に変更するには最小で $res 回の編集が必要") + + // 動的計画法 + res = editDistanceDP(s, t) + println("$s を $t に変更するには最小で $res 回の編集が必要") + + // 空間最適化後の動的計画法 + res = editDistanceDPComp(s, t) + println("$s を $t に変更するには最小で $res 回の編集が必要") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_dynamic_programming/knapsack.kt b/ja/codes/kotlin/chapter_dynamic_programming/knapsack.kt new file mode 100644 index 000000000..5d7200f66 --- /dev/null +++ b/ja/codes/kotlin/chapter_dynamic_programming/knapsack.kt @@ -0,0 +1,125 @@ +/** + * File: knapsack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.max + +/* 0-1 ナップサック:総当たり探索 */ +fun knapsackDFS( + wgt: IntArray, + _val: IntArray, + i: Int, + c: Int +): Int { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if (i == 0 || c == 0) { + return 0 + } + // ナップサック容量を超える場合は、入れない選択しかできない + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, _val, i - 1, c) + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + val no = knapsackDFS(wgt, _val, i - 1, c) + val yes = knapsackDFS(wgt, _val, i - 1, c - wgt[i - 1]) + _val[i - 1] + // 2つの案のうち価値が大きいほうを返す + return max(no, yes) +} + +/* 0-1 ナップサック:メモ化探索 */ +fun knapsackDFSMem( + wgt: IntArray, + _val: IntArray, + mem: Array, + i: Int, + c: Int +): Int { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if (i == 0 || c == 0) { + return 0 + } + // 既に記録があればそのまま返す + if (mem[i][c] != -1) { + return mem[i][c] + } + // ナップサック容量を超える場合は、入れない選択しかできない + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, _val, mem, i - 1, c) + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + val no = knapsackDFSMem(wgt, _val, mem, i - 1, c) + val yes = knapsackDFSMem(wgt, _val, mem, i - 1, c - wgt[i - 1]) + _val[i - 1] + // 2 つの案のうち価値が大きい方を記録して返す + mem[i][c] = max(no, yes) + return mem[i][c] +} + +/* 0-1 ナップサック:動的計画法 */ +fun knapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int { + val n = wgt.size + // dp テーブルを初期化 + val dp = Array(n + 1) { IntArray(cap + 1) } + // 状態遷移 + for (i in 1..n) { + for (c in 1..cap) { + if (wgt[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[i][c] = dp[i - 1][c] + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + _val[i - 1]) + } + } + } + return dp[n][cap] +} + +/* 0-1 ナップサック:空間最適化後の動的計画法 */ +fun knapsackDPComp(wgt: IntArray, _val: IntArray, cap: Int): Int { + val n = wgt.size + // dp テーブルを初期化 + val dp = IntArray(cap + 1) + // 状態遷移 + for (i in 1..n) { + // 逆順に走査する + for (c in cap downTo 1) { + if (wgt[i - 1] <= c) { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1]) + } + } + } + return dp[cap] +} + +/* Driver Code */ +fun main() { + val wgt = intArrayOf(10, 20, 30, 40, 50) + val _val = intArrayOf(50, 120, 150, 210, 240) + val cap = 50 + val n = wgt.size + + // 全探索 + var res = knapsackDFS(wgt, _val, n, cap) + println("ナップサック容量を超えない最大価値は $res") + + // メモ化探索 + val mem = Array(n + 1) { IntArray(cap + 1) } + for (row in mem) { + row.fill(-1) + } + res = knapsackDFSMem(wgt, _val, mem, n, cap) + println("ナップサック容量を超えない最大価値は $res") + + // 動的計画法 + res = knapsackDP(wgt, _val, cap) + println("ナップサック容量を超えない最大価値は $res") + + // 空間最適化後の動的計画法 + res = knapsackDPComp(wgt, _val, cap) + println("ナップサック容量を超えない最大価値は $res") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt b/ja/codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt new file mode 100644 index 000000000..8061c87af --- /dev/null +++ b/ja/codes/kotlin/chapter_dynamic_programming/min_cost_climbing_stairs_dp.kt @@ -0,0 +1,51 @@ +/** + * File: min_cost_climbing_stairs_dp.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.min + +/* 階段登りの最小コスト:動的計画法 */ +fun minCostClimbingStairsDP(cost: IntArray): Int { + val n = cost.size - 1 + if (n == 1 || n == 2) return cost[n] + // 部分問題の解を保存するために dp テーブルを初期化 + val dp = IntArray(n + 1) + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1] = cost[1] + dp[2] = cost[2] + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for (i in 3..n) { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] + } + return dp[n] +} + +/* 階段昇りの最小コスト:空間最適化後の動的計画法 */ +fun minCostClimbingStairsDPComp(cost: IntArray): Int { + val n = cost.size - 1 + if (n == 1 || n == 2) return cost[n] + var a = cost[1] + var b = cost[2] + for (i in 3..n) { + val tmp = b + b = min(a, tmp) + cost[i] + a = tmp + } + return b +} + +/* Driver Code */ +fun main() { + val cost = intArrayOf(0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1) + println("入力された階段コストのリストは ${cost.contentToString()}") + + var res = minCostClimbingStairsDP(cost) + println("階段を上り切る最小コストは $res") + + res = minCostClimbingStairsDPComp(cost) + println("階段を上り切る最小コストは $res") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_dynamic_programming/min_path_sum.kt b/ja/codes/kotlin/chapter_dynamic_programming/min_path_sum.kt new file mode 100644 index 000000000..5458676df --- /dev/null +++ b/ja/codes/kotlin/chapter_dynamic_programming/min_path_sum.kt @@ -0,0 +1,132 @@ +/** + * File: min_path_sum.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_dynamic_programming + +import kotlin.math.min + +/* 最小経路和:全探索 */ +fun minPathSumDFS(grid: Array, i: Int, j: Int): Int { + // 左上のセルなら探索を終了する + if (i == 0 && j == 0) { + return grid[0][0] + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if (i < 0 || j < 0) { + return Int.MAX_VALUE + } + // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する + val up = minPathSumDFS(grid, i - 1, j) + val left = minPathSumDFS(grid, i, j - 1) + // 左上隅から (i, j) までの最小経路コストを返す + return min(left, up) + grid[i][j] +} + +/* 最小経路和:メモ化探索 */ +fun minPathSumDFSMem( + grid: Array, + mem: Array, + i: Int, + j: Int +): Int { + // 左上のセルなら探索を終了する + if (i == 0 && j == 0) { + return grid[0][0] + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if (i < 0 || j < 0) { + return Int.MAX_VALUE + } + // 既に記録があればそのまま返す + if (mem[i][j] != -1) { + return mem[i][j] + } + // 左と上のセルからの最小経路コスト + val up = minPathSumDFSMem(grid, mem, i - 1, j) + val left = minPathSumDFSMem(grid, mem, i, j - 1) + // 左上から (i, j) までの最小経路コストを記録して返す + mem[i][j] = min(left, up) + grid[i][j] + return mem[i][j] +} + +/* 最小経路和:動的計画法 */ +fun minPathSumDP(grid: Array): Int { + val n = grid.size + val m = grid[0].size + // dp テーブルを初期化 + val dp = Array(n) { IntArray(m) } + dp[0][0] = grid[0][0] + // 状態遷移:先頭行 + for (j in 1..): Int { + val n = grid.size + val m = grid[0].size + // dp テーブルを初期化 + val dp = IntArray(m) + // 状態遷移:先頭行 + dp[0] = grid[0][0] + for (j in 1.. c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[i][c] = dp[i - 1][c] + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + _val[i - 1]) + } + } + } + return dp[n][cap] +} + +/* 完全ナップサック問題:空間最適化後の動的計画法 */ +fun unboundedKnapsackDPComp( + wgt: IntArray, + _val: IntArray, + cap: Int +): Int { + val n = wgt.size + // dp テーブルを初期化 + val dp = IntArray(cap + 1) + // 状態遷移 + for (i in 1..n) { + for (c in 1..cap) { + if (wgt[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[c] = dp[c] + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1]) + } + } + } + return dp[cap] +} + +/* Driver Code */ +fun main() { + val wgt = intArrayOf(1, 2, 3) + val _val = intArrayOf(5, 11, 15) + val cap = 4 + + // 動的計画法 + var res = unboundedKnapsackDP(wgt, _val, cap) + println("ナップサック容量を超えない最大価値は $res") + + // 空間最適化後の動的計画法 + res = unboundedKnapsackDPComp(wgt, _val, cap) + println("ナップサック容量を超えない最大価値は $res") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_graph/graph_adjacency_list.kt b/ja/codes/kotlin/chapter_graph/graph_adjacency_list.kt new file mode 100644 index 000000000..cc14cf959 --- /dev/null +++ b/ja/codes/kotlin/chapter_graph/graph_adjacency_list.kt @@ -0,0 +1,121 @@ +/** + * File: graph_adjacency_list.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_graph + +import utils.Vertex + +/* 隣接リストに基づく無向グラフクラス */ +class GraphAdjList(edges: Array>) { + // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 + val adjList = HashMap>() + + /* コンストラクタ */ + init { + // すべての頂点と辺を追加 + for (edge in edges) { + addVertex(edge[0]!!) + addVertex(edge[1]!!) + addEdge(edge[0]!!, edge[1]!!) + } + } + + /* 頂点数を取得 */ + fun size(): Int { + return adjList.size + } + + /* 辺を追加 */ + fun addEdge(vet1: Vertex, vet2: Vertex) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw IllegalArgumentException() + // 辺 vet1 - vet2 を追加 + adjList[vet1]?.add(vet2) + adjList[vet2]?.add(vet1) + } + + /* 辺を削除 */ + fun removeEdge(vet1: Vertex, vet2: Vertex) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw IllegalArgumentException() + // 辺 vet1 - vet2 を削除 + adjList[vet1]?.remove(vet2) + adjList[vet2]?.remove(vet1) + } + + /* 頂点を追加 */ + fun addVertex(vet: Vertex) { + if (adjList.containsKey(vet)) + return + // 隣接リストに新しいリストを追加 + adjList[vet] = mutableListOf() + } + + /* 頂点を削除 */ + fun removeVertex(vet: Vertex) { + if (!adjList.containsKey(vet)) + throw IllegalArgumentException() + // 隣接リストから頂点 vet に対応するリストを削除 + adjList.remove(vet) + // 他の頂点のリストを走査し、vet を含むすべての辺を削除 + for (list in adjList.values) { + list.remove(vet) + } + } + + /* 隣接リストを出力 */ + fun print() { + println("隣接リスト =") + for (pair in adjList.entries) { + val tmp = mutableListOf() + for (vertex in pair.value) { + tmp.add(vertex._val) + } + println("${pair.key._val}: $tmp,") + } + } +} + +/* Driver Code */ +fun main() { + /* 無向グラフを初期化 */ + val v = Vertex.valsToVets(intArrayOf(1, 3, 2, 5, 4)) + val edges = arrayOf( + arrayOf(v[0], v[1]), + arrayOf(v[0], v[3]), + arrayOf(v[1], v[2]), + arrayOf(v[2], v[3]), + arrayOf(v[2], v[4]), + arrayOf(v[3], v[4]) + ) + val graph = GraphAdjList(edges) + println("\n初期化後のグラフは") + graph.print() + + /* 辺を追加 */ + // 頂点 1, 2 は v[0], v[2] + graph.addEdge(v[0]!!, v[2]!!) + println("\n辺 1-2 を追加した後のグラフは") + graph.print() + + /* 辺を削除 */ + // 頂点 1, 3 は v[0], v[1] + graph.removeEdge(v[0]!!, v[1]!!) + println("\n辺 1-3 を削除した後のグラフは") + graph.print() + + /* 頂点を追加 */ + val v5 = Vertex(6) + graph.addVertex(v5) + println("\n頂点 6 を追加した後のグラフは") + graph.print() + + /* 頂点を削除 */ + // 頂点 3 は v[1] + graph.removeVertex(v[1]!!) + println("\n頂点 3 を削除した後のグラフは") + graph.print() +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_graph/graph_adjacency_matrix.kt b/ja/codes/kotlin/chapter_graph/graph_adjacency_matrix.kt new file mode 100644 index 000000000..7aa07c42e --- /dev/null +++ b/ja/codes/kotlin/chapter_graph/graph_adjacency_matrix.kt @@ -0,0 +1,134 @@ +/** + * File: graph_adjacency_matrix.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_graph + +import utils.printMatrix + +/* 隣接行列に基づく無向グラフクラス */ +class GraphAdjMat(vertices: IntArray, edges: Array) { + val vertices = mutableListOf() // 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す + val adjMat = mutableListOf>() // 隣接行列。行・列のインデックスは「頂点インデックス」に対応 + + /* コンストラクタ */ + init { + // 頂点を追加 + for (vertex in vertices) { + addVertex(vertex) + } + // 辺を追加 + // 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する + for (edge in edges) { + addEdge(edge[0], edge[1]) + } + } + + /* 頂点数を取得 */ + fun size(): Int { + return vertices.size + } + + /* 頂点を追加 */ + fun addVertex(_val: Int) { + val n = size() + // 頂点リストに新しい頂点の値を追加 + vertices.add(_val) + // 隣接行列に 1 行追加 + val newRow = mutableListOf() + for (j in 0..= size()) + throw IndexOutOfBoundsException() + // 頂点リストから index の頂点を削除する + vertices.removeAt(index) + // 隣接行列で index 行を削除する + adjMat.removeAt(index) + // 隣接行列で index 列を削除する + for (row in adjMat) { + row.removeAt(index) + } + } + + /* 辺を追加 */ + // 引数 i, j は vertices の要素インデックスに対応する + fun addEdge(i: Int, j: Int) { + // インデックスの範囲外と等値の処理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw IndexOutOfBoundsException() + // 無向グラフでは、隣接行列は主対角線に関して対称、すなわち (i, j) == (j, i) を満たす + adjMat[i][j] = 1 + adjMat[j][i] = 1 + } + + /* 辺を削除 */ + // 引数 i, j は vertices の要素インデックスに対応する + fun removeEdge(i: Int, j: Int) { + // インデックスの範囲外と等値の処理 + if (i < 0 || j < 0 || i >= size() || j >= size() || i == j) + throw IndexOutOfBoundsException() + adjMat[i][j] = 0 + adjMat[j][i] = 0 + } + + /* 隣接行列を出力 */ + fun print() { + print("頂点リスト = ") + println(vertices) + println("隣接行列 =") + printMatrix(adjMat) + } +} + +/* Driver Code */ +fun main() { + /* 無向グラフを初期化 */ + // edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意 + val vertices = intArrayOf(1, 3, 2, 5, 4) + val edges = arrayOf( + intArrayOf(0, 1), + intArrayOf(0, 3), + intArrayOf(1, 2), + intArrayOf(2, 3), + intArrayOf(2, 4), + intArrayOf(3, 4) + ) + val graph = GraphAdjMat(vertices, edges) + println("\n初期化後のグラフは") + graph.print() + + /* 辺を追加 */ + // 頂点 1, 2 のインデックスはそれぞれ 0, 2 + graph.addEdge(0, 2) + println("\n辺 1-2 を追加した後のグラフは") + graph.print() + + /* 辺を削除 */ + // 頂点 1, 3 のインデックスはそれぞれ 0, 1 + graph.removeEdge(0, 1) + println("\n辺 1-3 を削除した後のグラフは") + graph.print() + + /* 頂点を追加 */ + graph.addVertex(6) + println("\n頂点 6 を追加した後のグラフは") + graph.print() + + /* 頂点を削除 */ + // 頂点 3 のインデックスは 1 + graph.removeVertex(1) + println("\n頂点 3 を削除した後のグラフは") + graph.print() +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_graph/graph_bfs.kt b/ja/codes/kotlin/chapter_graph/graph_bfs.kt new file mode 100644 index 000000000..c9bda3a18 --- /dev/null +++ b/ja/codes/kotlin/chapter_graph/graph_bfs.kt @@ -0,0 +1,65 @@ +/** + * File: graph_bfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_graph + +import utils.Vertex +import java.util.* + +/* 幅優先探索 */ +// グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする +fun graphBFS(graph: GraphAdjList, startVet: Vertex): MutableList { + // 頂点の走査順序 + val res = mutableListOf() + // 訪問済み頂点を記録するためのハッシュ集合 + val visited = HashSet() + visited.add(startVet) + // BFS の実装にキューを用いる + val que = LinkedList() + que.offer(startVet) + // 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す + while (!que.isEmpty()) { + val vet = que.poll() // 先頭の頂点をデキュー + res.add(vet) // 訪問した頂点を記録 + // この頂点のすべての隣接頂点を走査 + for (adjVet in graph.adjList[vet]!!) { + if (visited.contains(adjVet)) + continue // 訪問済みの頂点をスキップ + que.offer(adjVet) // 未訪問の頂点のみをキューに追加 + visited.add(adjVet) // この頂点を訪問済みにする + } + } + // 頂点の走査順を返す + return res +} + +/* Driver Code */ +fun main() { + /* 無向グラフを初期化 */ + val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) + val edges = arrayOf( + arrayOf(v[0], v[1]), + arrayOf(v[0], v[3]), + arrayOf(v[1], v[2]), + arrayOf(v[1], v[4]), + arrayOf(v[2], v[5]), + arrayOf(v[3], v[4]), + arrayOf(v[3], v[6]), + arrayOf(v[4], v[5]), + arrayOf(v[4], v[7]), + arrayOf(v[5], v[8]), + arrayOf(v[6], v[7]), + arrayOf(v[7], v[8]) + ) + val graph = GraphAdjList(edges) + println("\n初期化後のグラフは") + graph.print() + + /* 幅優先探索 */ + val res = graphBFS(graph, v[0]!!) + println("\n幅優先探索(BFS)の頂点順序は") + println(Vertex.vetsToVals(res)) +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_graph/graph_dfs.kt b/ja/codes/kotlin/chapter_graph/graph_dfs.kt new file mode 100644 index 000000000..36d6d06ad --- /dev/null +++ b/ja/codes/kotlin/chapter_graph/graph_dfs.kt @@ -0,0 +1,60 @@ +/** + * File: graph_dfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_graph + +import utils.Vertex + +/* 深さ優先走査の補助関数 */ +fun dfs( + graph: GraphAdjList, + visited: MutableSet, + res: MutableList, + vet: Vertex? +) { + res.add(vet) // 訪問した頂点を記録 + visited.add(vet) // この頂点を訪問済みにする + // この頂点のすべての隣接頂点を走査 + for (adjVet in graph.adjList[vet]!!) { + if (visited.contains(adjVet)) + continue // 訪問済みの頂点をスキップ + // 隣接頂点を再帰的に訪問 + dfs(graph, visited, res, adjVet) + } +} + +/* 深さ優先探索 */ +// グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする +fun graphDFS(graph: GraphAdjList, startVet: Vertex?): MutableList { + // 頂点の走査順序 + val res = mutableListOf() + // 訪問済み頂点を記録するためのハッシュ集合 + val visited = HashSet() + dfs(graph, visited, res, startVet) + return res +} + +/* Driver Code */ +fun main() { + /* 無向グラフを初期化 */ + val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6)) + val edges = arrayOf( + arrayOf(v[0], v[1]), + arrayOf(v[0], v[3]), + arrayOf(v[1], v[2]), + arrayOf(v[2], v[5]), + arrayOf(v[4], v[5]), + arrayOf(v[5], v[6]) + ) + val graph = GraphAdjList(edges) + println("\n初期化後のグラフは") + graph.print() + + /* 深さ優先探索 */ + val res = graphDFS(graph, v[0]) + println("\n深さ優先探索(DFS)の頂点順序は") + println(Vertex.vetsToVals(res)) +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_greedy/coin_change_greedy.kt b/ja/codes/kotlin/chapter_greedy/coin_change_greedy.kt new file mode 100644 index 000000000..cd11259a3 --- /dev/null +++ b/ja/codes/kotlin/chapter_greedy/coin_change_greedy.kt @@ -0,0 +1,53 @@ +/** + * File: coin_change_greedy.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_greedy + +/* コイン交換:貪欲法 */ +fun coinChangeGreedy(coins: IntArray, amt: Int): Int { + // coins リストはソート済みと仮定する + var am = amt + var i = coins.size - 1 + var count = 0 + // 残額がなくなるまで貪欲選択を繰り返す + while (am > 0) { + // 残額以下で最も近い硬貨を見つける + while (i > 0 && coins[i] > am) { + i-- + } + // coins[i] を選択する + am -= coins[i] + count++ + } + // 実行可能な解が見つからなければ -1 を返す + return if (am == 0) count else -1 +} + +/* Driver Code */ +fun main() { + // 貪欲法:大域最適解を保証できる + var coins = intArrayOf(1, 5, 10, 20, 50, 100) + var amt = 186 + var res = coinChangeGreedy(coins, amt) + println("\ncoins = ${coins.contentToString()}, amt = $amt") + println("$amt を作るのに必要な最小硬貨枚数は $res") + + // 貪欲法:大域最適解を保証できない + coins = intArrayOf(1, 20, 50) + amt = 60 + res = coinChangeGreedy(coins, amt) + println("\ncoins = ${coins.contentToString()}, amt = $amt") + println("$amt を作るのに必要な最小硬貨枚数は $res") + println("実際に必要な最小枚数は 3、つまり 20 + 20 + 20") + + // 貪欲法:大域最適解を保証できない + coins = intArrayOf(1, 49, 50) + amt = 98 + res = coinChangeGreedy(coins, amt) + println("\ncoins = ${coins.contentToString()}, amt = $amt") + println("$amt を作るのに必要な最小硬貨枚数は $res") + println("実際に必要な最小枚数は 2、つまり 49 + 49") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_greedy/fractional_knapsack.kt b/ja/codes/kotlin/chapter_greedy/fractional_knapsack.kt new file mode 100644 index 000000000..73faeed9a --- /dev/null +++ b/ja/codes/kotlin/chapter_greedy/fractional_knapsack.kt @@ -0,0 +1,51 @@ +/** + * File: fractional_knapsack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_greedy + +/* 品物 */ +class Item( + val w: Int, // 品物 + val v: Int // 品物の価値 +) + +/* 分数ナップサック:貪欲法 */ +fun fractionalKnapsack(wgt: IntArray, _val: IntArray, c: Int): Double { + // 重さと価値の 2 属性を持つ品物リストを作成 + var cap = c + val items = arrayOfNulls(wgt.size) + for (i in wgt.indices) { + items[i] = Item(wgt[i], _val[i]) + } + // 単位価値 item.v / item.w の高い順にソートする + items.sortBy { item: Item? -> -(item!!.v.toDouble() / item.w) } + // 貪欲選択を繰り返す + var res = 0.0 + for (item in items) { + if (item!!.w <= cap) { + // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる + res += item.v + cap -= item.w + } else { + // 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる + res += item.v.toDouble() / item.w * cap + // 残り容量がないため、ループを抜ける + break + } + } + return res +} + +/* Driver Code */ +fun main() { + val wgt = intArrayOf(10, 20, 30, 40, 50) + val _val = intArrayOf(50, 120, 150, 210, 240) + val cap = 50 + + // 貪欲法 + val res = fractionalKnapsack(wgt, _val, cap) + println("ナップサック容量を超えない最大価値は $res") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_greedy/max_capacity.kt b/ja/codes/kotlin/chapter_greedy/max_capacity.kt new file mode 100644 index 000000000..e5558c2e3 --- /dev/null +++ b/ja/codes/kotlin/chapter_greedy/max_capacity.kt @@ -0,0 +1,41 @@ +/** + * File: max_capacity.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_greedy + +import kotlin.math.max +import kotlin.math.min + +/* 最大容量:貪欲法 */ +fun maxCapacity(ht: IntArray): Int { + // i, j を初期化し、それぞれ配列の両端に置く + var i = 0 + var j = ht.size - 1 + // 初期の最大容量は 0 + var res = 0 + // 2 枚の板が出会うまで貪欲選択を繰り返す + while (i < j) { + // 最大容量を更新する + val cap = min(ht[i], ht[j]) * (j - i) + res = max(res, cap) + // 短い方を内側へ動かす + if (ht[i] < ht[j]) { + i++ + } else { + j-- + } + } + return res +} + +/* Driver Code */ +fun main() { + val ht = intArrayOf(3, 8, 5, 2, 7, 7, 3, 4) + + // 貪欲法 + val res = maxCapacity(ht) + println("最大容量は $res") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_greedy/max_product_cutting.kt b/ja/codes/kotlin/chapter_greedy/max_product_cutting.kt new file mode 100644 index 000000000..65b61ef9a --- /dev/null +++ b/ja/codes/kotlin/chapter_greedy/max_product_cutting.kt @@ -0,0 +1,39 @@ +/** + * File: max_product_cutting.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_greedy + +import kotlin.math.pow + +/* 最大切断積:貪欲法 */ +fun maxProductCutting(n: Int): Int { + // n <= 3 のときは、必ず 1 を切り出す + if (n <= 3) { + return 1 * (n - 1) + } + // 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする + val a = n / 3 + val b = n % 3 + if (b == 1) { + // 余りが 1 のときは、1 * 3 を 2 * 2 に変える + return 3.0.pow((a - 1)).toInt() * 2 * 2 + } + if (b == 2) { + // 余りが 2 のときは、そのままにする + return 3.0.pow(a).toInt() * 2 * 2 + } + // 余りが 0 のときは、そのままにする + return 3.0.pow(a).toInt() +} + +/* Driver Code */ +fun main() { + val n = 58 + + // 貪欲法 + val res = maxProductCutting(n) + println("最大分割積は $res") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_hashing/array_hash_map.kt b/ja/codes/kotlin/chapter_hashing/array_hash_map.kt new file mode 100644 index 000000000..94e5cb1a8 --- /dev/null +++ b/ja/codes/kotlin/chapter_hashing/array_hash_map.kt @@ -0,0 +1,126 @@ +/** + * File: array_hash_map.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +/* キーと値の組 */ +class Pair( + var key: Int, + var _val: String +) + +/* 配列ベースのハッシュテーブル */ +class ArrayHashMap { + // 100 個のバケットを含む配列を初期化 + private val buckets = arrayOfNulls(100) + + /* ハッシュ関数 */ + fun hashFunc(key: Int): Int { + val index = key % 100 + return index + } + + /* 検索操作 */ + fun get(key: Int): String? { + val index = hashFunc(key) + val pair = buckets[index] ?: return null + return pair._val + } + + /* 追加操作 */ + fun put(key: Int, _val: String) { + val pair = Pair(key, _val) + val index = hashFunc(key) + buckets[index] = pair + } + + /* 削除操作 */ + fun remove(key: Int) { + val index = hashFunc(key) + // null に設定し、削除を表す + buckets[index] = null + } + + /* すべてのキーと値のペアを取得 */ + fun pairSet(): MutableList { + val pairSet = mutableListOf() + for (pair in buckets) { + if (pair != null) + pairSet.add(pair) + } + return pairSet + } + + /* すべてのキーを取得 */ + fun keySet(): MutableList { + val keySet = mutableListOf() + for (pair in buckets) { + if (pair != null) + keySet.add(pair.key) + } + return keySet + } + + /* すべての値を取得 */ + fun valueSet(): MutableList { + val valueSet = mutableListOf() + for (pair in buckets) { + if (pair != null) + valueSet.add(pair._val) + } + return valueSet + } + + /* ハッシュテーブルを出力 */ + fun print() { + for (kv in pairSet()) { + val key = kv.key + val _val = kv._val + println("$key -> $_val") + } + } +} + +/* Driver Code */ +fun main() { + /* ハッシュテーブルを初期化 */ + val map = ArrayHashMap() + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.put(12836, "シャオハー") + map.put(15937, "シャオルオ") + map.put(16750, "シャオスワン") + map.put(13276, "シャオファー") + map.put(10583, "シャオヤ") + println("\n追加完了後、ハッシュテーブルは\nKey -> Value") + map.print() + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + val name = map.get(15937) + println("\n学籍番号 15937 を入力すると、名前 $name が見つかりました") + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + map.remove(10583) + println("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value") + map.print() + + /* ハッシュテーブルを走査 */ + println("\nキーと値のペアを走査 Key -> Value") + for (kv in map.pairSet()) { + println("${kv.key} -> ${kv._val}") + } + println("\nキー Key を個別に走査") + for (key in map.keySet()) { + println(key) + } + println("\n値 Value を個別に走査") + for (_val in map.valueSet()) { + println(_val) + } +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_hashing/built_in_hash.kt b/ja/codes/kotlin/chapter_hashing/built_in_hash.kt new file mode 100644 index 000000000..0fd728797 --- /dev/null +++ b/ja/codes/kotlin/chapter_hashing/built_in_hash.kt @@ -0,0 +1,36 @@ +/** + * File: built_in_hash.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +import utils.ListNode + +/* Driver Code */ +fun main() { + val num = 3 + val hashNum = num.hashCode() + println("整数 $num のハッシュ値は $hashNum") + + val bol = true + val hashBol = bol.hashCode() + println("真偽値 $bol のハッシュ値は $hashBol") + + val dec = 3.14159 + val hashDec = dec.hashCode() + println("小数 $dec のハッシュ値は $hashDec") + + val str = "Hello アルゴリズム" + val hashStr = str.hashCode() + println("文字列 $str のハッシュ値は $hashStr") + + val arr = arrayOf(12836, "シャオハー") + val hashTup = arr.contentHashCode() + println("配列 ${arr.contentToString()} のハッシュ値は $hashTup") + + val obj = ListNode(0) + val hashObj = obj.hashCode() + println("ノードオブジェクト $obj のハッシュ値は $hashObj") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_hashing/hash_map.kt b/ja/codes/kotlin/chapter_hashing/hash_map.kt new file mode 100644 index 000000000..20304e42c --- /dev/null +++ b/ja/codes/kotlin/chapter_hashing/hash_map.kt @@ -0,0 +1,50 @@ +/** + * File: hash_map.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +import utils.printHashMap + +/* Driver Code */ +fun main() { + /* ハッシュテーブルを初期化 */ + val map = HashMap() + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map[12836] = "シャオハー" + map[15937] = "シャオルオ" + map[16750] = "シャオスワン" + map[13276] = "シャオファー" + map[10583] = "シャオヤ" + println("\n追加完了後、ハッシュテーブルは\nKey -> Value") + printHashMap(map) + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + val name = map[15937] + println("\n学籍番号 15937 を入力すると、名前 $name が見つかりました") + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + map.remove(10583) + println("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value") + printHashMap(map) + + /* ハッシュテーブルを走査 */ + println("\nキーと値のペアを走査 Key->Value") + for ((key, value) in map) { + println("$key -> $value") + } + println("\nキー Key を個別に走査") + for (key in map.keys) { + println(key) + } + println("\n値 Value を個別に走査") + for (_val in map.values) { + println(_val) + } +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_hashing/hash_map_chaining.kt b/ja/codes/kotlin/chapter_hashing/hash_map_chaining.kt new file mode 100644 index 000000000..4c79e8351 --- /dev/null +++ b/ja/codes/kotlin/chapter_hashing/hash_map_chaining.kt @@ -0,0 +1,145 @@ +/** + * File: hash_map_chaining.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +/* チェイン法ハッシュテーブル */ +class HashMapChaining { + var size: Int // キーと値のペア数 + var capacity: Int // ハッシュテーブル容量 + val loadThres: Double // リサイズを発動する負荷率のしきい値 + val extendRatio: Int // 拡張倍率 + var buckets: MutableList> // バケット配列 + + /* コンストラクタ */ + init { + size = 0 + capacity = 4 + loadThres = 2.0 / 3.0 + extendRatio = 2 + buckets = mutableListOf() + for (i in 0.. loadThres) { + extend() + } + val index = hashFunc(key) + val bucket = buckets[index] + // バケットを走査し、指定した key が見つかれば対応する val を更新して返す + for (pair in bucket) { + if (pair.key == key) { + pair._val = _val + return + } + } + // その key が存在しなければ、キーと値のペアを末尾に追加 + val pair = Pair(key, _val) + bucket.add(pair) + size++ + } + + /* 削除操作 */ + fun remove(key: Int) { + val index = hashFunc(key) + val bucket = buckets[index] + // バケットを走査してキーと値のペアを削除 + for (pair in bucket) { + if (pair.key == key) { + bucket.remove(pair) + size-- + break + } + } + } + + /* ハッシュテーブルを拡張 */ + fun extend() { + // 元のハッシュテーブルを一時保存 + val bucketsTmp = buckets + // リサイズ後の新しいハッシュテーブルを初期化 + capacity *= extendRatio + // mutablelist には固定サイズがない + buckets = mutableListOf() + for (i in 0..() + for (pair in bucket) { + val k = pair.key + val v = pair._val + res.add("$k -> $v") + } + println(res) + } + } +} + +/* Driver Code */ +fun main() { + /* ハッシュテーブルを初期化 */ + val map = HashMapChaining() + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.put(12836, "シャオハー") + map.put(15937, "シャオルオ") + map.put(16750, "シャオスワン") + map.put(13276, "シャオファー") + map.put(10583, "シャオヤ") + println("\n追加完了後、ハッシュテーブルは\nKey -> Value") + map.print() + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + val name = map.get(13276) + println("\n学籍番号 13276 を入力すると、名前 $name が見つかりました") + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + map.remove(12836) + println("\n12836 を削除した後、ハッシュテーブルは\nKey -> Value") + map.print() +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_hashing/hash_map_open_addressing.kt b/ja/codes/kotlin/chapter_hashing/hash_map_open_addressing.kt new file mode 100644 index 000000000..50f1a97c2 --- /dev/null +++ b/ja/codes/kotlin/chapter_hashing/hash_map_open_addressing.kt @@ -0,0 +1,161 @@ +/** + * File: hash_map_open_addressing.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +/* オープンアドレス法ハッシュテーブル */ +class HashMapOpenAddressing { + private var size: Int // キーと値のペア数 + private var capacity: Int // ハッシュテーブル容量 + private val loadThres: Double // リサイズを発動する負荷率のしきい値 + private val extendRatio: Int // 拡張倍率 + private var buckets: Array // バケット配列 + private val TOMBSTONE: Pair // 削除済みマーク + + /* コンストラクタ */ + init { + size = 0 + capacity = 4 + loadThres = 2.0 / 3.0 + extendRatio = 2 + buckets = arrayOfNulls(capacity) + TOMBSTONE = Pair(-1, "-1") + } + + /* ハッシュ関数 */ + fun hashFunc(key: Int): Int { + return key % capacity + } + + /* 負荷率 */ + fun loadFactor(): Double { + return (size / capacity).toDouble() + } + + /* key に対応するバケットインデックスを探す */ + fun findBucket(key: Int): Int { + var index = hashFunc(key) + var firstTombstone = -1 + // 線形プロービングを行い、空バケットに達したら終了 + while (buckets[index] != null) { + // key が見つかったら、対応するバケットのインデックスを返す + if (buckets[index]?.key == key) { + // 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 + if (firstTombstone != -1) { + buckets[firstTombstone] = buckets[index] + buckets[index] = TOMBSTONE + return firstTombstone // 移動後のバケットインデックスを返す + } + return index // バケットのインデックスを返す + } + // 最初に見つかった削除マークを記録 + if (firstTombstone == -1 && buckets[index] == TOMBSTONE) { + firstTombstone = index + } + // バケットのインデックスを計算し、末尾を越えたら先頭に戻る + index = (index + 1) % capacity + } + // key が存在しない場合は追加位置のインデックスを返す + return if (firstTombstone == -1) index else firstTombstone + } + + /* 検索操作 */ + fun get(key: Int): String? { + // key に対応するバケットインデックスを探す + val index = findBucket(key) + // キーと値の組が見つかったら、対応する val を返す + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + return buckets[index]?._val + } + // キーと値の組が存在しなければ null を返す + return null + } + + /* 追加操作 */ + fun put(key: Int, _val: String) { + // 負荷率がしきい値を超えたら、リサイズを実行 + if (loadFactor() > loadThres) { + extend() + } + // key に対応するバケットインデックスを探す + val index = findBucket(key) + // キーと値の組が見つかったら、val を上書きして返す + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index]!!._val = _val + return + } + // キーと値の組が存在しない場合は、その組を追加する + buckets[index] = Pair(key, _val) + size++ + } + + /* 削除操作 */ + fun remove(key: Int) { + // key に対応するバケットインデックスを探す + val index = findBucket(key) + // キーと値の組が見つかったら、削除マーカーで上書きする + if (buckets[index] != null && buckets[index] != TOMBSTONE) { + buckets[index] = TOMBSTONE + size-- + } + } + + /* ハッシュテーブルを拡張 */ + fun extend() { + // 元のハッシュテーブルを一時保存 + val bucketsTmp = buckets + // リサイズ後の新しいハッシュテーブルを初期化 + capacity *= extendRatio + buckets = arrayOfNulls(capacity) + size = 0 + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す + for (pair in bucketsTmp) { + if (pair != null && pair != TOMBSTONE) { + put(pair.key, pair._val) + } + } + } + + /* ハッシュテーブルを出力 */ + fun print() { + for (pair in buckets) { + if (pair == null) { + println("null") + } else if (pair == TOMBSTONE) { + println("TOMESTOME") + } else { + println("${pair.key} -> ${pair._val}") + } + } + } +} + +/* Driver Code */ +fun main() { + // ハッシュテーブルを初期化 + val hashmap = HashMapOpenAddressing() + + // 追加操作 + // ハッシュテーブルにキーと値の組 (key, val) を追加する + hashmap.put(12836, "シャオハー") + hashmap.put(15937, "シャオルオ") + hashmap.put(16750, "シャオスワン") + hashmap.put(13276, "シャオファー") + hashmap.put(10583, "シャオヤー") + println("\n追加完了後、ハッシュテーブルは\nKey -> Value") + hashmap.print() + + // 検索操作 + // ハッシュテーブルにキー key を入力し、値 val を得る + val name = hashmap.get(13276) + println("\n学籍番号 13276 を入力すると、名前 $name が見つかりました") + + // 削除操作 + // ハッシュテーブルからキーと値の組 (key, val) を削除する + hashmap.remove(16750) + println("\n16750 を削除した後、ハッシュテーブルは\nKey -> Value") + hashmap.print() +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_hashing/simple_hash.kt b/ja/codes/kotlin/chapter_hashing/simple_hash.kt new file mode 100644 index 000000000..51d3377b7 --- /dev/null +++ b/ja/codes/kotlin/chapter_hashing/simple_hash.kt @@ -0,0 +1,64 @@ +/** + * File: simple_hash.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_hashing + +/* 加算ハッシュ */ +fun addHash(key: String): Int { + var hash = 0L + val MODULUS = 1000000007 + for (c in key.toCharArray()) { + hash = (hash + c.code) % MODULUS + } + return hash.toInt() +} + +/* 乗算ハッシュ */ +fun mulHash(key: String): Int { + var hash = 0L + val MODULUS = 1000000007 + for (c in key.toCharArray()) { + hash = (31 * hash + c.code) % MODULUS + } + return hash.toInt() +} + +/* XOR ハッシュ */ +fun xorHash(key: String): Int { + var hash = 0 + val MODULUS = 1000000007 + for (c in key.toCharArray()) { + hash = hash xor c.code + } + return hash and MODULUS +} + +/* 回転ハッシュ */ +fun rotHash(key: String): Int { + var hash = 0L + val MODULUS = 1000000007 + for (c in key.toCharArray()) { + hash = ((hash shl 4) xor (hash shr 28) xor c.code.toLong()) % MODULUS + } + return hash.toInt() +} + +/* Driver Code */ +fun main() { + val key = "Hello アルゴリズム" + + var hash = addHash(key) + println("加算ハッシュ値は $hash") + + hash = mulHash(key) + println("乗算ハッシュ値は $hash") + + hash = xorHash(key) + println("XORハッシュ値は $hash") + + hash = rotHash(key) + println("回転ハッシュ値は $hash") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_heap/heap.kt b/ja/codes/kotlin/chapter_heap/heap.kt new file mode 100644 index 000000000..5cc38e2f8 --- /dev/null +++ b/ja/codes/kotlin/chapter_heap/heap.kt @@ -0,0 +1,66 @@ +/** + * File: heap.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_heap + +import utils.printHeap +import java.util.* + +fun testPush(heap: Queue, _val: Int) { + heap.offer(_val) // 要素をヒープに追加 + print("\n要素 $_val をヒープに追加した後\n") + printHeap(heap) +} + +fun testPop(heap: Queue) { + val _val = heap.poll() // ヒープ頂点の要素を取り出す + print("\nヒープ先頭要素 $_val を取り出した後\n") + printHeap(heap) +} + +/* Driver Code */ +fun main() { + /* ヒープを初期化 */ + // 最小ヒープを初期化 + var minHeap = PriorityQueue() + + // 最大ヒープを初期化する(lambda 式で Comparator を変更すればよい) + val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } + + println("\n以下のテストケースは最大ヒープです") + + /* 要素をヒープに追加 */ + testPush(maxHeap, 1) + testPush(maxHeap, 3) + testPush(maxHeap, 2) + testPush(maxHeap, 5) + testPush(maxHeap, 4) + + /* ヒープ頂点の要素を取得 */ + val peek = maxHeap.peek() + print("\nヒープ先頭要素は $peek\n") + + /* ヒープ頂点の要素を取り出す */ + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + testPop(maxHeap) + + /* ヒープのサイズを取得 */ + val size = maxHeap.size + print("\nヒープ要素数は $size\n") + + /* ヒープが空かどうかを判定 */ + val isEmpty = maxHeap.isEmpty() + print("\nヒープが空かどうか $isEmpty\n") + + /* リストを入力してヒープを構築 */ + // 時間計算量は O(n) であり、O(nlogn) ではない + minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) + println("\n入力リストから最小ヒープを構築した後") + printHeap(minHeap) +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_heap/my_heap.kt b/ja/codes/kotlin/chapter_heap/my_heap.kt new file mode 100644 index 000000000..d188cdee6 --- /dev/null +++ b/ja/codes/kotlin/chapter_heap/my_heap.kt @@ -0,0 +1,160 @@ +/** + * File: my_heap.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_heap + +import utils.printHeap +import java.util.* + +/* 最大ヒープ */ +class MaxHeap(nums: MutableList?) { + // 配列ではなくリストを使うことで、拡張を考慮する必要がない + private val maxHeap = mutableListOf() + + /* コンストラクタ。入力リストに基づいてヒープを構築する */ + init { + // リスト要素をそのままヒープに追加 + maxHeap.addAll(nums!!) + // 葉ノード以外のすべてのノードをヒープ化 + for (i in parent(size() - 1) downTo 0) { + siftDown(i) + } + } + + /* 左子ノードのインデックスを取得 */ + private fun left(i: Int): Int { + return 2 * i + 1 + } + + /* 右子ノードのインデックスを取得 */ + private fun right(i: Int): Int { + return 2 * i + 2 + } + + /* 親ノードのインデックスを取得 */ + private fun parent(i: Int): Int { + return (i - 1) / 2 // 切り捨て除算 + } + + /* 要素を交換 */ + private fun swap(i: Int, j: Int) { + val temp = maxHeap[i] + maxHeap[i] = maxHeap[j] + maxHeap[j] = temp + } + + /* ヒープのサイズを取得 */ + fun size(): Int { + return maxHeap.size + } + + /* ヒープが空かどうかを判定 */ + fun isEmpty(): Boolean { + /* ヒープが空かどうかを判定 */ + return size() == 0 + } + + /* ヒープ先頭要素にアクセス */ + fun peek(): Int { + return maxHeap[0] + } + + /* 要素をヒープに追加 */ + fun push(_val: Int) { + // ノードを追加 + maxHeap.add(_val) + // 下から上へヒープ化 + siftUp(size() - 1) + } + + /* ノード i から始めて、下から上へヒープ化 */ + private fun siftUp(it: Int) { + // Kotlin の関数引数は不変のため、一時変数を作成する + var i = it + while (true) { + // ノード i の親ノードを取得 + val p = parent(i) + // 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 + if (p < 0 || maxHeap[i] <= maxHeap[p]) break + // 2 つのノードを交換 + swap(i, p) + // ループで下から上へヒープ化 + i = p + } + } + + /* 要素をヒープから取り出す */ + fun pop(): Int { + // 空判定の処理 + if (isEmpty()) throw IndexOutOfBoundsException() + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + swap(0, size() - 1) + // ノードを削除 + val _val = maxHeap.removeAt(size() - 1) + // 上から下へヒープ化 + siftDown(0) + // ヒープ先頭要素を返す + return _val + } + + /* ノード i から始めて、上から下へヒープ化 */ + private fun siftDown(it: Int) { + // Kotlin の関数引数は不変のため、一時変数を作成する + var i = it + while (true) { + // ノード i, l, r のうち値が最大のノードを ma とする + val l = left(i) + val r = right(i) + var ma = i + if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l + if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + if (ma == i) break + // 2 つのノードを交換 + swap(i, ma) + // ループで上から下へヒープ化 + i = ma + } + } + + /* ヒープ(二分木)を出力 */ + fun print() { + val queue = PriorityQueue { a: Int, b: Int -> b - a } + queue.addAll(maxHeap) + printHeap(queue) + } +} + +/* Driver Code */ +fun main() { + /* 最大ヒープを初期化 */ + val maxHeap = MaxHeap(mutableListOf(9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2)) + println("\n入力リストからヒープを構築した後") + maxHeap.print() + + /* ヒープ頂点の要素を取得 */ + var peek = maxHeap.peek() + print("\nヒープ先頭要素は $peek\n") + + /* 要素をヒープに追加 */ + val _val = 7 + maxHeap.push(_val) + print("\n要素 $_val をヒープに追加した後\n") + maxHeap.print() + + /* ヒープ頂点の要素を取り出す */ + peek = maxHeap.pop() + print("\nヒープ先頭要素 $peek を取り出した後\n") + maxHeap.print() + + /* ヒープのサイズを取得 */ + val size = maxHeap.size() + print("\nヒープ要素数は $size\n") + + /* ヒープが空かどうかを判定 */ + val isEmpty = maxHeap.isEmpty() + print("\nヒープが空かどうか $isEmpty\n") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_heap/top_k.kt b/ja/codes/kotlin/chapter_heap/top_k.kt new file mode 100644 index 000000000..5313322b4 --- /dev/null +++ b/ja/codes/kotlin/chapter_heap/top_k.kt @@ -0,0 +1,38 @@ +/** + * File: top_k.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_heap + +import utils.printHeap +import java.util.* + +/* ヒープに基づいて配列中の最大の k 個の要素を探す */ +fun topKHeap(nums: IntArray, k: Int): Queue { + // 最小ヒープを初期化 + val heap = PriorityQueue() + // 配列の先頭 k 個の要素をヒープに追加 + for (i in 0.. heap.peek()) { + heap.poll() + heap.offer(nums[i]) + } + } + return heap +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(1, 7, 6, 3, 2) + val k = 3 + val res = topKHeap(nums, k) + println("最大の $k 個の要素は") + printHeap(res) +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_searching/binary_search.kt b/ja/codes/kotlin/chapter_searching/binary_search.kt new file mode 100644 index 000000000..b98f0087b --- /dev/null +++ b/ja/codes/kotlin/chapter_searching/binary_search.kt @@ -0,0 +1,59 @@ +/** + * File: binary_search.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +/* 二分探索(両閉区間) */ +fun binarySearch(nums: IntArray, target: Int): Int { + // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す + var i = 0 + var j = nums.size - 1 + // ループし、探索区間が空になったら終了する(i > j で空) + while (i <= j) { + val m = i + (j - i) / 2 // 中点インデックス m を計算 + if (nums[m] < target) // この場合、target は区間 [m+1, j] にある + i = m + 1 + else if (nums[m] > target) // この場合、target は区間 [i, m-1] にある + j = m - 1 + else // 目標要素が見つかったらそのインデックスを返す + return m + } + // 目標要素が見つからなければ -1 を返す + return -1 +} + +/* 二分探索(左閉右開区間) */ +fun binarySearchLCRO(nums: IntArray, target: Int): Int { + // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す + var i = 0 + var j = nums.size + // ループし、探索区間が空になったら終了する(i = j で空) + while (i < j) { + val m = i + (j - i) / 2 // 中点インデックス m を計算 + if (nums[m] < target) // この場合、target は区間 [m+1, j) にある + i = m + 1 + else if (nums[m] > target) // この場合、target は区間 [i, m) にある + j = m + else // 目標要素が見つかったらそのインデックスを返す + return m + } + // 目標要素が見つからなければ -1 を返す + return -1 +} + +/* Driver Code */ +fun main() { + val target = 6 + val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) + + /* 二分探索(両閉区間) */ + var index = binarySearch(nums, target) + println("対象要素 6 のインデックス = $index") + + /* 二分探索(左閉右開区間) */ + index = binarySearchLCRO(nums, target) + println("対象要素 6 のインデックス = $index") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_searching/binary_search_edge.kt b/ja/codes/kotlin/chapter_searching/binary_search_edge.kt new file mode 100644 index 000000000..4543b4826 --- /dev/null +++ b/ja/codes/kotlin/chapter_searching/binary_search_edge.kt @@ -0,0 +1,48 @@ +/** + * File: binary_search_edge.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +/* 最も左の target を二分探索 */ +fun binarySearchLeftEdge(nums: IntArray, target: Int): Int { + // target の挿入位置を探すのと等価 + val i = binarySearchInsertion(nums, target) + // target が見つからなければ、-1 を返す + if (i == nums.size || nums[i] != target) { + return -1 + } + // target が見つかったら、インデックス i を返す + return i +} + +/* 最も右の target を二分探索 */ +fun binarySearchRightEdge(nums: IntArray, target: Int): Int { + // 最左の target + 1 を探す問題に変換する + val i = binarySearchInsertion(nums, target + 1) + // j は最も右の target を指し、i は target より大きい最初の要素を指す + val j = i - 1 + // target が見つからなければ、-1 を返す + if (j == -1 || nums[j] != target) { + return -1 + } + // target が見つかったら、インデックス j を返す + return j +} + +/* Driver Code */ +fun main() { + // 重複要素を含む配列 + val nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15) + println("\n配列 nums = ${nums.contentToString()}") + + // 二分探索で左端と右端を探す + for (target in intArrayOf(6, 7)) { + var index = binarySearchLeftEdge(nums, target) + println("最も左にある要素 $target のインデックスは $index") + index = binarySearchRightEdge(nums, target) + println("最も右にある要素 $target のインデックスは $index") + } +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_searching/binary_search_insertion.kt b/ja/codes/kotlin/chapter_searching/binary_search_insertion.kt new file mode 100644 index 000000000..e9a04a2ea --- /dev/null +++ b/ja/codes/kotlin/chapter_searching/binary_search_insertion.kt @@ -0,0 +1,65 @@ +/** + * File: binary_search_insertion.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +/* 二分探索で挿入位置を探す(重複要素なし) */ +fun binarySearchInsertionSimple(nums: IntArray, target: Int): Int { + var i = 0 + var j = nums.size - 1 // 両閉区間 [0, n-1] を初期化 + while (i <= j) { + val m = i + (j - i) / 2 // 中点インデックス m を計算 + if (nums[m] < target) { + i = m + 1 // target は区間 [m+1, j] にある + } else if (nums[m] > target) { + j = m - 1 // target は区間 [i, m-1] にある + } else { + return m // target が見つかったら、挿入位置 m を返す + } + } + // target が見つからなければ、挿入位置 i を返す + return i +} + +/* 二分探索で挿入位置を探す(重複要素あり) */ +fun binarySearchInsertion(nums: IntArray, target: Int): Int { + var i = 0 + var j = nums.size - 1 // 両閉区間 [0, n-1] を初期化 + while (i <= j) { + val m = i + (j - i) / 2 // 中点インデックス m を計算 + if (nums[m] < target) { + i = m + 1 // target は区間 [m+1, j] にある + } else if (nums[m] > target) { + j = m - 1 // target は区間 [i, m-1] にある + } else { + j = m - 1 // target より小さい最初の要素は区間 [i, m-1] にある + } + } + // 挿入位置 i を返す + return i +} + +/* Driver Code */ +fun main() { + // 重複要素のない配列 + var nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35) + println("\n配列 nums = ${nums.contentToString()}") + // 二分探索で挿入位置を探す + for (target in intArrayOf(6, 9)) { + val index = binarySearchInsertionSimple(nums, target) + println("要素 $target の挿入位置のインデックスは $index") + } + + // 重複要素を含む配列 + nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15) + println("\n配列 nums = ${nums.contentToString()}") + + // 二分探索で挿入位置を探す + for (target in intArrayOf(2, 6, 20)) { + val index = binarySearchInsertion(nums, target) + println("要素 $target の挿入位置のインデックスは $index") + } +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_searching/hashing_search.kt b/ja/codes/kotlin/chapter_searching/hashing_search.kt new file mode 100644 index 000000000..b307c5202 --- /dev/null +++ b/ja/codes/kotlin/chapter_searching/hashing_search.kt @@ -0,0 +1,49 @@ +/** + * File: hashing_search.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +import utils.ListNode + +/* ハッシュ探索(配列) */ +fun hashingSearchArray(map: Map, target: Int): Int { + // ハッシュテーブルの key: 対象要素、_val: インデックス + // ハッシュテーブルにその key がなければ -1 を返す + return map.getOrDefault(target, -1) +} + +/* ハッシュ探索(連結リスト) */ +fun hashingSearchLinkedList(map: Map, target: Int): ListNode? { + // ハッシュテーブルの key: 対象ノードの値、_val: ノードオブジェクト + // ハッシュテーブルにその key がなければ null を返す + return map.getOrDefault(target, null) +} + +/* Driver Code */ +fun main() { + val target = 3 + + /* ハッシュ探索(配列) */ + val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8) + // ハッシュテーブルを初期化 + val map = HashMap() + for (i in nums.indices) { + map[nums[i]] = i // key: 要素、_val: インデックス + } + val index = hashingSearchArray(map, target) + println("目標要素 3 のインデックス = $index") + + /* ハッシュ探索(連結リスト) */ + var head = ListNode.arrToLinkedList(nums) + // ハッシュテーブルを初期化 + val map1 = HashMap() + while (head != null) { + map1[head._val] = head // key: ノード値、_val: ノード + head = head.next + } + val node = hashingSearchLinkedList(map1, target) + println("目標ノード値 3 に対応するノードオブジェクトは $node") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_searching/linear_search.kt b/ja/codes/kotlin/chapter_searching/linear_search.kt new file mode 100644 index 000000000..723650c14 --- /dev/null +++ b/ja/codes/kotlin/chapter_searching/linear_search.kt @@ -0,0 +1,50 @@ +/** + * File: linear_search.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +import utils.ListNode + +/* 線形探索(配列) */ +fun linearSearchArray(nums: IntArray, target: Int): Int { + // 配列を走査 + for (i in nums.indices) { + // 目標要素が見つかったらそのインデックスを返す + if (nums[i] == target) + return i + } + // 目標要素が見つからなければ -1 を返す + return -1 +} + +/* 線形探索(連結リスト) */ +fun linearSearchLinkedList(h: ListNode?, target: Int): ListNode? { + // 連結リストを走査 + var head = h + while (head != null) { + // 対象ノードが見つかったら、それを返す + if (head._val == target) + return head + head = head.next + } + // 対象ノードが見つからない場合は null を返す + return null +} + +/* Driver Code */ +fun main() { + val target = 3 + + /* 配列で線形探索を行う */ + val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8) + val index = linearSearchArray(nums, target) + println("目標要素 3 のインデックス = $index") + + /* 連結リストで線形探索を行う */ + val head = ListNode.arrToLinkedList(nums) + val node = linearSearchLinkedList(head, target) + println("目標ノード値 3 に対応するノードオブジェクトは $node") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_searching/two_sum.kt b/ja/codes/kotlin/chapter_searching/two_sum.kt new file mode 100644 index 000000000..a62756e2e --- /dev/null +++ b/ja/codes/kotlin/chapter_searching/two_sum.kt @@ -0,0 +1,49 @@ +/** + * File: two_sum.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_searching + +/* 方法 1:総当たり列挙 */ +fun twoSumBruteForce(nums: IntArray, target: Int): IntArray { + val size = nums.size + // 2重ループのため、時間計算量は O(n^2) + for (i in 0..() + // 単一ループで、時間計算量は O(n) + for (i in 0.. nums[j + 1]) { + // nums[j] と nums[j + 1] を交換 + val temp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = temp + } + } + } +} + +/* バブルソート(フラグ最適化) */ +fun bubbleSortWithFlag(nums: IntArray) { + // 外側のループ:未ソート区間は [0, i] + for (i in nums.size - 1 downTo 1) { + var flag = false // フラグを初期化する + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for (j in 0.. nums[j + 1]) { + // nums[j] と nums[j + 1] を交換 + val temp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = temp + flag = true // 交換する要素を記録 + } + } + if (!flag) break // このバブル処理で要素交換が一度もなければそのまま終了 + } +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(4, 1, 3, 1, 5, 2) + bubbleSort(nums) + println("バブルソート完了後 nums = ${nums.contentToString()}") + + val nums1 = intArrayOf(4, 1, 3, 1, 5, 2) + bubbleSortWithFlag(nums1) + println("バブルソート完了後 nums1 = ${nums1.contentToString()}") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_sorting/bucket_sort.kt b/ja/codes/kotlin/chapter_sorting/bucket_sort.kt new file mode 100644 index 000000000..c47c59a18 --- /dev/null +++ b/ja/codes/kotlin/chapter_sorting/bucket_sort.kt @@ -0,0 +1,44 @@ +/** + * File: bucket_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* バケットソート */ +fun bucketSort(nums: FloatArray) { + // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする + val k = nums.size / 2 + val buckets = mutableListOf>() + for (i in 0.. nums[ma]) + ma = l + if (r < n && nums[r] > nums[ma]) + ma = r + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + if (ma == i) + break + // 2 つのノードを交換 + val temp = nums[i] + nums[i] = nums[ma] + nums[ma] = temp + // ループで上から下へヒープ化 + i = ma + } +} + +/* ヒープソート */ +fun heapSort(nums: IntArray) { + // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する + for (i in nums.size / 2 - 1 downTo 0) { + siftDown(nums, nums.size, i) + } + // ヒープから最大要素を取り出し、n-1 回繰り返す + for (i in nums.size - 1 downTo 1) { + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + val temp = nums[0] + nums[0] = nums[i] + nums[i] = temp + // 根ノードを起点に、上から下へヒープ化 + siftDown(nums, i, 0) + } +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(4, 1, 3, 1, 5, 2) + heapSort(nums) + println("ヒープソート完了後 nums = ${nums.contentToString()}") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_sorting/insertion_sort.kt b/ja/codes/kotlin/chapter_sorting/insertion_sort.kt new file mode 100644 index 000000000..687610ced --- /dev/null +++ b/ja/codes/kotlin/chapter_sorting/insertion_sort.kt @@ -0,0 +1,29 @@ +/** + * File: insertion_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 挿入ソート */ +fun insertionSort(nums: IntArray) { + // 外側ループ: ソート済み要素は 1, 2, ..., n + for (i in nums.indices) { + val base = nums[i] + var j = i - 1 + // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j] // nums[j] を 1 つ右へ移動する + j-- + } + nums[j + 1] = base // base を正しい位置に配置する + } +} + +/* Driver Code */ +fun main() { + val nums = intArrayOf(4, 1, 3, 1, 5, 2) + insertionSort(nums) + println("挿入ソート完了後 nums = ${nums.contentToString()}") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_sorting/merge_sort.kt b/ja/codes/kotlin/chapter_sorting/merge_sort.kt new file mode 100644 index 000000000..8a26d4d09 --- /dev/null +++ b/ja/codes/kotlin/chapter_sorting/merge_sort.kt @@ -0,0 +1,56 @@ +/** + * File: merge_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 左部分配列と右部分配列をマージ */ +fun merge(nums: IntArray, left: Int, mid: Int, right: Int) { + // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] + // マージ結果を格納する一時配列 tmp を作成 + val tmp = IntArray(right - left + 1) + // 左右の部分配列の開始インデックスを初期化する + var i = left + var j = mid + 1 + var k = 0 + // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) + tmp[k++] = nums[i++] + else + tmp[k++] = nums[j++] + } + // 左右の部分配列の残り要素を一時配列にコピーする + while (i <= mid) { + tmp[k++] = nums[i++] + } + while (j <= right) { + tmp[k++] = nums[j++] + } + // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする + for (l in tmp.indices) { + nums[left + l] = tmp[l] + } +} + +/* マージソート */ +fun mergeSort(nums: IntArray, left: Int, right: Int) { + // 終了条件 + if (left >= right) return // 部分配列の長さが 1 になったら再帰を終了 + // 分割フェーズ + val mid = left + (right - left) / 2 // 中点を計算 + mergeSort(nums, left, mid) // 左部分配列を再帰処理 + mergeSort(nums, mid + 1, right) // 右部分配列を再帰処理 + // マージフェーズ + merge(nums, left, mid, right) +} + +/* Driver Code */ +fun main() { + /* マージソート */ + val nums = intArrayOf(7, 3, 2, 6, 0, 1, 5, 4) + mergeSort(nums, 0, nums.size - 1) + println("マージソート完了後 nums = ${nums.contentToString()}") +} diff --git a/ja/codes/kotlin/chapter_sorting/quick_sort.kt b/ja/codes/kotlin/chapter_sorting/quick_sort.kt new file mode 100644 index 000000000..bb4712ec0 --- /dev/null +++ b/ja/codes/kotlin/chapter_sorting/quick_sort.kt @@ -0,0 +1,121 @@ +/** + * File: quick_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 要素の交換 */ +fun swap(nums: IntArray, i: Int, j: Int) { + val temp = nums[i] + nums[i] = nums[j] + nums[j] = temp +} + +/* 番兵分割 */ +fun partition(nums: IntArray, left: Int, right: Int): Int { + // nums[left] を基準値とする + var i = left + var j = right + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j-- // 右から左へ基準値未満の最初の要素を探す + while (i < j && nums[i] <= nums[left]) + i++ // 左から右へ基準値より大きい最初の要素を探す + swap(nums, i, j) // この 2 つの要素を交換 + } + swap(nums, i, left) // 基準値を 2 つの部分配列の境界へ交換する + return i // 基準値のインデックスを返す +} + +/* クイックソート */ +fun quickSort(nums: IntArray, left: Int, right: Int) { + // 部分配列の長さが 1 なら再帰を終了する + if (left >= right) return + // 番兵分割 + val pivot = partition(nums, left, right) + // 左右の部分配列を再帰処理 + quickSort(nums, left, pivot - 1) + quickSort(nums, pivot + 1, right) +} + +/* 3つの候補要素の中央値を選ぶ */ +fun medianThree(nums: IntArray, left: Int, mid: Int, right: Int): Int { + val l = nums[left] + val m = nums[mid] + val r = nums[right] + if ((m in l..r) || (m in r..l)) + return mid // m は l と r の間 + if ((l in m..r) || (l in r..m)) + return left // l は m と r の間 + return right +} + +/* 番兵による分割処理(3 点中央値) */ +fun partitionMedian(nums: IntArray, left: Int, right: Int): Int { + // 3つの候補要素の中央値を選ぶ + val med = medianThree(nums, left, (left + right) / 2, right) + // 中央値を配列の最左端に交換する + swap(nums, left, med) + // nums[left] を基準値とする + var i = left + var j = right + while (i < j) { + while (i < j && nums[j] >= nums[left]) + j-- // 右から左へ基準値未満の最初の要素を探す + while (i < j && nums[i] <= nums[left]) + i++ // 左から右へ基準値より大きい最初の要素を探す + swap(nums, i, j) // この 2 つの要素を交換 + } + swap(nums, i, left) // 基準値を 2 つの部分配列の境界へ交換する + return i // 基準値のインデックスを返す +} + +/* クイックソート */ +fun quickSortMedian(nums: IntArray, left: Int, right: Int) { + // 部分配列の長さが 1 なら再帰を終了する + if (left >= right) return + // 番兵分割 + val pivot = partitionMedian(nums, left, right) + // 左右の部分配列を再帰処理 + quickSort(nums, left, pivot - 1) + quickSort(nums, pivot + 1, right) +} + +/* クイックソート(再帰深度最適化) */ +fun quickSortTailCall(nums: IntArray, left: Int, right: Int) { + // 部分配列の長さが 1 なら終了 + var l = left + var r = right + while (l < r) { + // 番兵による分割処理 + val pivot = partition(nums, l, r) + // 2 つの部分配列のうち短いほうにクイックソートを適用する + if (pivot - l < r - pivot) { + quickSort(nums, l, pivot - 1) // 左部分配列を再帰的にソート + l = pivot + 1 // 未ソート区間の残りは [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, r) // 右部分配列を再帰的にソート + r = pivot - 1 // 未ソート区間の残りは [left, pivot - 1] + } + } +} + +/* Driver Code */ +fun main() { + /* クイックソート */ + val nums = intArrayOf(2, 4, 1, 0, 3, 5) + quickSort(nums, 0, nums.size - 1) + println("クイックソート完了後 nums = ${nums.contentToString()}") + + /* クイックソート(中央値の基準値で最適化) */ + val nums1 = intArrayOf(2, 4, 1, 0, 3, 5) + quickSortMedian(nums1, 0, nums1.size - 1) + println("クイックソート(中央値ピボット最適化)完了後 nums1 = ${nums1.contentToString()}") + + /* クイックソート(再帰深度最適化) */ + val nums2 = intArrayOf(2, 4, 1, 0, 3, 5) + quickSortTailCall(nums2, 0, nums2.size - 1) + println("クイックソート(再帰深度最適化)完了後 nums2 = ${nums2.contentToString()}") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_sorting/radix_sort.kt b/ja/codes/kotlin/chapter_sorting/radix_sort.kt new file mode 100644 index 000000000..73cc5a545 --- /dev/null +++ b/ja/codes/kotlin/chapter_sorting/radix_sort.kt @@ -0,0 +1,68 @@ +/** + * File: radix_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */ +fun digit(num: Int, exp: Int): Int { + // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す + return (num / exp) % 10 +} + +/* 計数ソート(nums の k 桁目でソート) */ +fun countingSortDigit(nums: IntArray, exp: Int) { + // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 + val counter = IntArray(10) + val n = nums.size + // 0~9 の各数字の出現回数を集計する + for (i in 0.. m) m = num + var exp = 1 + // 下位桁から上位桁の順に走査する + while (exp <= m) { + // 配列要素の k 桁目に対して計数ソートを行う + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // つまり exp = 10^(k-1) + countingSortDigit(nums, exp) + exp *= 10 + } +} + +/* Driver Code */ +fun main() { + // 基数ソート + val nums = intArrayOf( + 10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996 + ) + radixSort(nums) + println("基数ソート完了後 nums = ${nums.contentToString()}") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_sorting/selection_sort.kt b/ja/codes/kotlin/chapter_sorting/selection_sort.kt new file mode 100644 index 000000000..25ae0c3a3 --- /dev/null +++ b/ja/codes/kotlin/chapter_sorting/selection_sort.kt @@ -0,0 +1,32 @@ +/** + * File: selection_sort.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_sorting + +/* 選択ソート */ +fun selectionSort(nums: IntArray) { + val n = nums.size + // 外側ループ:未整列区間は [i, n-1] + for (i in 0..() + + /* スタックの長さを取得 */ + fun size(): Int { + return stack.size + } + + /* スタックが空かどうかを判定 */ + fun isEmpty(): Boolean { + return size() == 0 + } + + /* プッシュ */ + fun push(num: Int) { + stack.add(num) + } + + /* ポップ */ + fun pop(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return stack.removeAt(size() - 1) + } + + /* スタックトップの要素にアクセス */ + fun peek(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return stack[size() - 1] + } + + /* List を Array に変換して返す */ + fun toArray(): Array { + return stack.toTypedArray() + } +} + +/* Driver Code */ +fun main() { + /* スタックを初期化 */ + val stack = ArrayStack() + + /* 要素をプッシュ */ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + println("スタック stack = ${stack.toArray().contentToString()}") + + /* スタックトップの要素にアクセス */ + val peek = stack.peek() + println("スタックトップ要素 peek = $peek") + + /* 要素をポップ */ + val pop = stack.pop() + println("ポップした要素 pop = $pop、ポップ後の stack = ${stack.toArray().contentToString()}") + + /* スタックの長さを取得 */ + val size = stack.size() + println("スタックの長さ size = $size") + + /* 空かどうかを判定 */ + val isEmpty = stack.isEmpty() + println("スタックが空かどうか = $isEmpty") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_stack_and_queue/deque.kt b/ja/codes/kotlin/chapter_stack_and_queue/deque.kt new file mode 100644 index 000000000..33c82597e --- /dev/null +++ b/ja/codes/kotlin/chapter_stack_and_queue/deque.kt @@ -0,0 +1,45 @@ +/** + * File: deque.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +import java.util.* + +/* Driver Code */ +fun main() { + /* 両端キューを初期化 */ + val deque = LinkedList() + deque.offerLast(3) + deque.offerLast(2) + deque.offerLast(5) + println("両端キュー deque = $deque") + + /* 要素にアクセス */ + val peekFirst = deque.peekFirst() + println("先頭要素 peekFirst = $peekFirst") + val peekLast = deque.peekLast() + println("末尾要素 peekLast = $peekLast") + + /* 要素をエンキュー */ + deque.offerLast(4) + println("要素 4 を末尾に追加後 deque = $deque") + deque.offerFirst(1) + println("要素 1 を先頭に追加後 deque = $deque") + + /* 要素をデキュー */ + val popLast = deque.pollLast() + println("末尾から取り出した要素 = $popLast、取り出し後 deque = $deque") + val popFirst = deque.pollFirst() + println("先頭から取り出した要素 = $popFirst、取り出し後 deque = $deque") + + /* 両端キューの長さを取得 */ + val size = deque.size + println("双方向キューの長さ size = $size") + + /* 両端キューが空かどうかを判定 */ + val isEmpty = deque.isEmpty() + println("双方向キューが空かどうか = $isEmpty") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt b/ja/codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt new file mode 100644 index 000000000..4aba13f7b --- /dev/null +++ b/ja/codes/kotlin/chapter_stack_and_queue/linkedlist_deque.kt @@ -0,0 +1,163 @@ +/** + * File: linkedlist_deque.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +/* 双方向連結リストノード */ +class ListNode(var _val: Int) { + // ノード値 + var next: ListNode? = null // 後続ノードへの参照 + var prev: ListNode? = null // 前駆ノードへの参照 +} + +/* 双方向連結リストベースの両端キュー */ +class LinkedListDeque { + private var front: ListNode? = null // 先頭ノード front + private var rear: ListNode? = null // 末尾ノード rear + private var queSize: Int = 0 // 両端キューの長さ + + /* 両端キューの長さを取得 */ + fun size(): Int { + return queSize + } + + /* 両端キューが空かどうかを判定 */ + fun isEmpty(): Boolean { + return size() == 0 + } + + /* エンキュー操作 */ + fun push(num: Int, isFront: Boolean) { + val node = ListNode(num) + // 連結リストが空なら、front と rear の両方を node に向ける + if (isEmpty()) { + rear = node + front = rear + // 先頭へのエンキュー操作 + } else if (isFront) { + // node を連結リストの先頭に追加 + front?.prev = node + node.next = front + front = node // 先頭ノードを更新する + // 末尾へのエンキュー操作 + } else { + // node を連結リストの末尾に追加 + rear?.next = node + node.prev = rear + rear = node // 末尾ノードを更新する + } + queSize++ // キューの長さを更新 + } + + /* キュー先頭にエンキュー */ + fun pushFirst(num: Int) { + push(num, true) + } + + /* キュー末尾にエンキュー */ + fun pushLast(num: Int) { + push(num, false) + } + + /* デキュー操作 */ + fun pop(isFront: Boolean): Int { + if (isEmpty()) + throw IndexOutOfBoundsException() + val _val: Int + // キュー先頭からの取り出し + if (isFront) { + _val = front!!._val // 先頭ノードの値を一時保存 + // 先頭ノードを削除 + val fNext = front!!.next + if (fNext != null) { + fNext.prev = null + front!!.next = null + } + front = fNext // 先頭ノードを更新する + // キュー末尾からの取り出し + } else { + _val = rear!!._val // 末尾ノードの値を一時保存 + // 末尾ノードを削除 + val rPrev = rear!!.prev + if (rPrev != null) { + rPrev.next = null + rear!!.prev = null + } + rear = rPrev // 末尾ノードを更新する + } + queSize-- // キューの長さを更新 + return _val + } + + /* キュー先頭からデキュー */ + fun popFirst(): Int { + return pop(true) + } + + /* キュー末尾からデキュー */ + fun popLast(): Int { + return pop(false) + } + + /* キュー先頭の要素にアクセス */ + fun peekFirst(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return front!!._val + } + + /* キュー末尾の要素にアクセス */ + fun peekLast(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return rear!!._val + } + + /* 出力用の配列を返す */ + fun toArray(): IntArray { + var node = front + val res = IntArray(size()) + for (i in res.indices) { + res[i] = node!!._val + node = node.next + } + return res + } +} + +/* Driver Code */ +fun main() { + /* 両端キューを初期化 */ + val deque = LinkedListDeque() + deque.pushLast(3) + deque.pushLast(2) + deque.pushLast(5) + println("双方向キュー deque = ${deque.toArray().contentToString()}") + + /* 要素にアクセス */ + val peekFirst = deque.peekFirst() + println("先頭要素 peekFirst = $peekFirst") + val peekLast = deque.peekLast() + println("末尾要素 peekLast = $peekLast") + + /* 要素をエンキュー */ + deque.pushLast(4) + println("要素 4 を末尾に追加した後 deque = ${deque.toArray().contentToString()}") + deque.pushFirst(1) + println("要素 1 を先頭に追加した後 deque = ${deque.toArray().contentToString()}") + + /* 要素をデキュー */ + val popLast = deque.popLast() + println("末尾から取り出した要素 = ${popLast}、末尾から取り出した後 deque = ${deque.toArray().contentToString()}") + val popFirst = deque.popFirst() + println("先頭から取り出した要素 = ${popFirst}、先頭から取り出した後 deque = ${deque.toArray().contentToString()}") + + /* 両端キューの長さを取得 */ + val size = deque.size() + println("双方向キューの長さ size = $size") + + /* 両端キューが空かどうかを判定 */ + val isEmpty = deque.isEmpty() + println("双方向キューが空かどうか = $isEmpty") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt b/ja/codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt new file mode 100644 index 000000000..5811ae4e2 --- /dev/null +++ b/ja/codes/kotlin/chapter_stack_and_queue/linkedlist_queue.kt @@ -0,0 +1,98 @@ +/** + * File: linkedlist_queue.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +/* 連結リストベースのキュー */ +class LinkedListQueue( + // 先頭ノード front、末尾ノード rear + private var front: ListNode? = null, + private var rear: ListNode? = null, + private var queSize: Int = 0 +) { + + /* キューの長さを取得 */ + fun size(): Int { + return queSize + } + + /* キューが空かどうかを判定 */ + fun isEmpty(): Boolean { + return size() == 0 + } + + /* エンキュー */ + fun push(num: Int) { + // 末尾ノードの後ろに num を追加 + val node = ListNode(num) + // キューが空なら、先頭・末尾ノードをともにそのノードに設定 + if (front == null) { + front = node + rear = node + // キューが空でなければ、そのノードを末尾ノードの後ろに追加 + } else { + rear?.next = node + rear = node + } + queSize++ + } + + /* デキュー */ + fun pop(): Int { + val num = peek() + // 先頭ノードを削除 + front = front?.next + queSize-- + return num + } + + /* キュー先頭の要素にアクセス */ + fun peek(): Int { + if (isEmpty()) throw IndexOutOfBoundsException() + return front!!._val + } + + /* 連結リストを Array に変換して返す */ + fun toArray(): IntArray { + var node = front + val res = IntArray(size()) + for (i in res.indices) { + res[i] = node!!._val + node = node.next + } + return res + } +} + +/* Driver Code */ +fun main() { + /* キューを初期化 */ + val queue = LinkedListQueue() + + /* 要素をエンキュー */ + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + println("キュー queue = ${queue.toArray().contentToString()}") + + /* キュー先頭の要素にアクセス */ + val peek = queue.peek() + println("先頭要素 peek = $peek") + + /* 要素をデキュー */ + val pop = queue.pop() + println("デキューした要素 pop = $pop、デキュー後 queue = ${queue.toArray().contentToString()}") + + /* キューの長さを取得 */ + val size = queue.size() + println("キューの長さ size = $size") + + /* キューが空かどうかを判定 */ + val isEmpty = queue.isEmpty() + println("キューが空かどうか = $isEmpty") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt b/ja/codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt new file mode 100644 index 000000000..c0c98f033 --- /dev/null +++ b/ja/codes/kotlin/chapter_stack_and_queue/linkedlist_stack.kt @@ -0,0 +1,87 @@ +/** + * File: linkedlist_stack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +/* 連結リストベースのスタック */ +class LinkedListStack( + private var stackPeek: ListNode? = null, // 先頭ノードをスタックトップとする + private var stkSize: Int = 0 // スタックの長さ +) { + + /* スタックの長さを取得 */ + fun size(): Int { + return stkSize + } + + /* スタックが空かどうかを判定 */ + fun isEmpty(): Boolean { + return size() == 0 + } + + /* プッシュ */ + fun push(num: Int) { + val node = ListNode(num) + node.next = stackPeek + stackPeek = node + stkSize++ + } + + /* ポップ */ + fun pop(): Int? { + val num = peek() + stackPeek = stackPeek?.next + stkSize-- + return num + } + + /* スタックトップの要素にアクセス */ + fun peek(): Int? { + if (isEmpty()) throw IndexOutOfBoundsException() + return stackPeek?._val + } + + /* List を Array に変換して返す */ + fun toArray(): IntArray { + var node = stackPeek + val res = IntArray(size()) + for (i in res.size - 1 downTo 0) { + res[i] = node?._val!! + node = node.next + } + return res + } +} + +/* Driver Code */ +fun main() { + /* スタックを初期化 */ + val stack = LinkedListStack() + + /* 要素をプッシュ */ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + println("スタック stack = ${stack.toArray().contentToString()}") + + /* スタックトップの要素にアクセス */ + val peek = stack.peek()!! + println("スタックトップ要素 peek = $peek") + + /* 要素をポップ */ + val pop = stack.pop()!! + println("ポップした要素 pop = $pop、ポップ後の stack = ${stack.toArray().contentToString()}") + + /* スタックの長さを取得 */ + val size = stack.size() + println("スタックの長さ size = $size") + + /* 空かどうかを判定 */ + val isEmpty = stack.isEmpty() + println("スタックが空かどうか = $isEmpty") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_stack_and_queue/queue.kt b/ja/codes/kotlin/chapter_stack_and_queue/queue.kt new file mode 100644 index 000000000..f5ef5fc1b --- /dev/null +++ b/ja/codes/kotlin/chapter_stack_and_queue/queue.kt @@ -0,0 +1,39 @@ +/** + * File: queue.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +import java.util.* + +/* Driver Code */ +fun main() { + /* キューを初期化 */ + val queue = LinkedList() + + /* 要素をエンキュー */ + queue.offer(1) + queue.offer(3) + queue.offer(2) + queue.offer(5) + queue.offer(4) + println("キュー queue = $queue") + + /* キュー先頭の要素にアクセス */ + val peek = queue.peek() + println("先頭要素 peek = $peek") + + /* 要素をデキュー */ + val pop = queue.poll() + println("デキューした要素 pop = $pop、デキュー後 queue = $queue") + + /* キューの長さを取得 */ + val size = queue.size + println("キューの長さ size = $size") + + /* キューが空かどうかを判定 */ + val isEmpty = queue.isEmpty() + println("キューが空かどうか = $isEmpty") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_stack_and_queue/stack.kt b/ja/codes/kotlin/chapter_stack_and_queue/stack.kt new file mode 100644 index 000000000..2b8fa9c3c --- /dev/null +++ b/ja/codes/kotlin/chapter_stack_and_queue/stack.kt @@ -0,0 +1,39 @@ +/** + * File: stack.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_stack_and_queue + +import java.util.* + +/* Driver Code */ +fun main() { + /* スタックを初期化 */ + val stack = Stack() + + /* 要素をプッシュ */ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + println("スタック stack = $stack") + + /* スタックトップの要素にアクセス */ + val peek = stack.peek() + println("スタックトップ要素 peek = $peek") + + /* 要素をポップ */ + val pop = stack.pop() + println("ポップした要素 pop = $pop、ポップ後 stack = $stack") + + /* スタックの長さを取得 */ + val size = stack.size + println("スタックの長さ size = $size") + + /* 空かどうかを判定 */ + val isEmpty = stack.isEmpty() + println("スタックが空かどうか = $isEmpty") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_tree/array_binary_tree.kt b/ja/codes/kotlin/chapter_tree/array_binary_tree.kt new file mode 100644 index 000000000..0d732ec5c --- /dev/null +++ b/ja/codes/kotlin/chapter_tree/array_binary_tree.kt @@ -0,0 +1,127 @@ +/** + * File: array_binary_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree + +/* 配列表現による二分木クラス */ +class ArrayBinaryTree(private val tree: MutableList) { + /* リスト容量 */ + fun size(): Int { + return tree.size + } + + /* インデックス i のノードの値を取得 */ + fun _val(i: Int): Int? { + // インデックスが範囲外なら、空きを表す null を返す + if (i < 0 || i >= size()) return null + return tree[i] + } + + /* インデックス i のノードの左子ノードのインデックスを取得 */ + fun left(i: Int): Int { + return 2 * i + 1 + } + + /* インデックス i のノードの右子ノードのインデックスを取得 */ + fun right(i: Int): Int { + return 2 * i + 2 + } + + /* インデックス i のノードの親ノードのインデックスを取得 */ + fun parent(i: Int): Int { + return (i - 1) / 2 + } + + /* レベル順走査 */ + fun levelOrder(): MutableList { + val res = mutableListOf() + // 配列を直接走査する + for (i in 0..) { + // 空きスロットなら返す + if (_val(i) == null) + return + // 先行順走査 + if ("pre" == order) + res.add(_val(i)) + dfs(left(i), order, res) + // 中順走査 + if ("in" == order) + res.add(_val(i)) + dfs(right(i), order, res) + // 後順走査 + if ("post" == order) + res.add(_val(i)) + } + + /* 先行順走査 */ + fun preOrder(): MutableList { + val res = mutableListOf() + dfs(0, "pre", res) + return res + } + + /* 中順走査 */ + fun inOrder(): MutableList { + val res = mutableListOf() + dfs(0, "in", res) + return res + } + + /* 後順走査 */ + fun postOrder(): MutableList { + val res = mutableListOf() + dfs(0, "post", res) + return res + } +} + +/* Driver Code */ +fun main() { + // 二分木を初期化する + // ここでは、リストから二分木を直接生成する関数を利用する + val arr = mutableListOf(1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15) + + val root = TreeNode.listToTree(arr) + println("\n二分木を初期化\n") + println("二分木の配列表現:") + println(arr) + println("二分木の連結リスト表現:") + printTree(root) + + // 配列表現による二分木クラス + val abt = ArrayBinaryTree(arr) + + // ノードにアクセス + val i = 1 + val l = abt.left(i) + val r = abt.right(i) + val p = abt.parent(i) + println("現在のノードのインデックスは $i 、値は ${abt._val(i)}") + println("左の子ノードのインデックスは $l 、値は ${abt._val(l)}") + println("右の子ノードのインデックスは $r 、値は ${abt._val(r)}") + println("親ノードのインデックスは $p 、値は ${abt._val(p)}") + + // 木を走査 + var res = abt.levelOrder() + println("\nレベル順走査:$res") + res = abt.preOrder() + println("先行順走査:$res") + res = abt.inOrder() + println("中間順走査:$res") + res = abt.postOrder() + println("後行順走査:$res") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_tree/avl_tree.kt b/ja/codes/kotlin/chapter_tree/avl_tree.kt new file mode 100644 index 000000000..f01acbaaf --- /dev/null +++ b/ja/codes/kotlin/chapter_tree/avl_tree.kt @@ -0,0 +1,223 @@ +/** + * File: avl_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree +import kotlin.math.max + +/* AVL 木 */ +class AVLTree { + var root: TreeNode? = null // 根ノード + + /* ノードの高さを取得 */ + fun height(node: TreeNode?): Int { + // 空ノードの高さは -1、葉ノードの高さは 0 + return node?.height ?: -1 + } + + /* ノードの高さを更新する */ + private fun updateHeight(node: TreeNode?) { + // ノードの高さは最も高い部分木の高さ + 1 に等しい + node?.height = max(height(node?.left), height(node?.right)) + 1 + } + + /* 平衡係数を取得 */ + fun balanceFactor(node: TreeNode?): Int { + // 空ノードの平衡係数は 0 + if (node == null) return 0 + // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ + return height(node.left) - height(node.right) + } + + /* 右回転 */ + private fun rightRotate(node: TreeNode?): TreeNode { + val child = node!!.left + val grandChild = child!!.right + // child を支点として node を右回転させる + child.right = node + node.left = grandChild + // ノードの高さを更新する + updateHeight(node) + updateHeight(child) + // 回転後の部分木の根ノードを返す + return child + } + + /* 左回転 */ + private fun leftRotate(node: TreeNode?): TreeNode { + val child = node!!.right + val grandChild = child!!.left + // child を支点として node を左回転させる + child.left = node + node.right = grandChild + // ノードの高さを更新する + updateHeight(node) + updateHeight(child) + // 回転後の部分木の根ノードを返す + return child + } + + /* 回転操作を行い、この部分木の平衡を回復する */ + private fun rotate(node: TreeNode): TreeNode { + // ノード node の平衡係数を取得 + val balanceFactor = balanceFactor(node) + // 左に偏った木 + if (balanceFactor > 1) { + if (balanceFactor(node.left) >= 0) { + // 右回転 + return rightRotate(node) + } else { + // 左回転してから右回転 + node.left = leftRotate(node.left) + return rightRotate(node) + } + } + // 右に偏った木 + if (balanceFactor < -1) { + if (balanceFactor(node.right) <= 0) { + // 左回転 + return leftRotate(node) + } else { + // 右回転してから左回転 + node.right = rightRotate(node.right) + return leftRotate(node) + } + } + // 平衡木なので回転不要、そのまま返す + return node + } + + /* ノードを挿入 */ + fun insert(_val: Int) { + root = insertHelper(root, _val) + } + + /* ノードを再帰的に挿入する(補助メソッド) */ + private fun insertHelper(n: TreeNode?, _val: Int): TreeNode { + if (n == null) + return TreeNode(_val) + var node = n + /* 1. 挿入位置を探索してノードを挿入 */ + if (_val < node._val) + node.left = insertHelper(node.left, _val) + else if (_val > node._val) + node.right = insertHelper(node.right, _val) + else + return node // 重複ノードは挿入せず、そのまま返す + updateHeight(node) // ノードの高さを更新する + /* 2. 回転操作を行い、部分木の平衡を回復する */ + node = rotate(node) + // 部分木の根ノードを返す + return node + } + + /* ノードを削除 */ + fun remove(_val: Int) { + root = removeHelper(root, _val) + } + + /* ノードを再帰的に削除する(補助メソッド) */ + private fun removeHelper(n: TreeNode?, _val: Int): TreeNode? { + var node = n ?: return null + /* 1. ノードを探索して削除 */ + if (_val < node._val) + node.left = removeHelper(node.left, _val) + else if (_val > node._val) + node.right = removeHelper(node.right, _val) + else { + if (node.left == null || node.right == null) { + val child = if (node.left != null) + node.left + else + node.right + // 子ノード数 = 0 の場合、node をそのまま削除して返す + if (child == null) + return null + // 子ノード数 = 1 の場合、node をそのまま削除する + else + node = child + } else { + // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える + var temp = node.right + while (temp!!.left != null) { + temp = temp.left + } + node.right = removeHelper(node.right, temp._val) + node._val = temp._val + } + } + updateHeight(node) // ノードの高さを更新する + /* 2. 回転操作を行い、部分木の平衡を回復する */ + node = rotate(node) + // 部分木の根ノードを返す + return node + } + + /* ノードを探索 */ + fun search(_val: Int): TreeNode? { + var cur = root + // ループで探索し、葉ノードを越えたら抜ける + while (cur != null) { + // 目標ノードは cur の右部分木にある + cur = if (cur._val < _val) + cur.right!! + // 目標ノードは cur の左部分木にある + else if (cur._val > _val) + cur.left + // 目標ノードが見つかったらループを抜ける + else + break + } + // 目標ノードを返す + return cur + } +} + +fun testInsert(tree: AVLTree, _val: Int) { + tree.insert(_val) + println("\nノード $_val を挿入後、AVL 木は") + printTree(tree.root) +} + +fun testRemove(tree: AVLTree, _val: Int) { + tree.remove(_val) + println("\nノード $_val を削除後、AVL 木は") + printTree(tree.root) +} + +/* Driver Code */ +fun main() { + /* 空の AVL 木を初期化する */ + val avlTree = AVLTree() + + /* ノードを挿入 */ + // ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい + testInsert(avlTree, 1) + testInsert(avlTree, 2) + testInsert(avlTree, 3) + testInsert(avlTree, 4) + testInsert(avlTree, 5) + testInsert(avlTree, 8) + testInsert(avlTree, 7) + testInsert(avlTree, 9) + testInsert(avlTree, 10) + testInsert(avlTree, 6) + + /* 重複ノードを挿入する */ + testInsert(avlTree, 7) + + /* ノードを削除 */ + // ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい + testRemove(avlTree, 8) // 次数 0 のノードを削除する + testRemove(avlTree, 5) // 次数 1 のノードを削除する + testRemove(avlTree, 4) // 次数 2 のノードを削除する + + /* ノードを検索 */ + val node = avlTree.search(7) + println("\n 見つかったノードオブジェクトは $node、ノードの値 = ${node?._val}") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_tree/binary_search_tree.kt b/ja/codes/kotlin/chapter_tree/binary_search_tree.kt new file mode 100644 index 000000000..0dc01e488 --- /dev/null +++ b/ja/codes/kotlin/chapter_tree/binary_search_tree.kt @@ -0,0 +1,157 @@ +/** + * File: binary_search_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree + +/* 二分探索木 */ +class BinarySearchTree { + // 空の木を初期化する + private var root: TreeNode? = null + + /* 二分木の根ノードを取得 */ + fun getRoot(): TreeNode? { + return root + } + + /* ノードを探索 */ + fun search(num: Int): TreeNode? { + var cur = root + // ループで探索し、葉ノードを越えたら抜ける + while (cur != null) { + // 目標ノードは cur の右部分木にある + cur = if (cur._val < num) + cur.right + // 目標ノードは cur の左部分木にある + else if (cur._val > num) + cur.left + // 目標ノードが見つかったらループを抜ける + else + break + } + // 目標ノードを返す + return cur + } + + /* ノードを挿入 */ + fun insert(num: Int) { + // 木が空なら、根ノードを初期化する + if (root == null) { + root = TreeNode(num) + return + } + var cur = root + var pre: TreeNode? = null + // ループで探索し、葉ノードを越えたら抜ける + while (cur != null) { + // 重複ノードが見つかったら、直ちに返す + if (cur._val == num) + return + pre = cur + // 挿入位置は cur の右部分木にある + cur = if (cur._val < num) + cur.right + // 挿入位置は cur の左部分木にある + else + cur.left + } + // ノードを挿入 + val node = TreeNode(num) + if (pre?._val!! < num) + pre.right = node + else + pre.left = node + } + + /* ノードを削除 */ + fun remove(num: Int) { + // 木が空なら、そのまま早期リターンする + if (root == null) + return + var cur = root + var pre: TreeNode? = null + // ループで探索し、葉ノードを越えたら抜ける + while (cur != null) { + // 削除対象のノードが見つかったら、ループを抜ける + if (cur._val == num) + break + pre = cur + // 削除対象ノードは cur の右部分木にある + cur = if (cur._val < num) + cur.right + // 削除対象ノードは cur の左部分木にある + else + cur.left + } + // 削除対象ノードがなければそのまま返す + if (cur == null) + return + // 子ノード数 = 0 or 1 + if (cur.left == null || cur.right == null) { + // 子ノード数が 0 / 1 のとき、child = null / その子ノード + val child = if (cur.left != null) + cur.left + else + cur.right + // ノード cur を削除する + if (cur != root) { + if (pre!!.left == cur) + pre.left = child + else + pre.right = child + } else { + // 削除ノードが根ノードなら、根ノードを再設定 + root = child + } + // 子ノード数 = 2 + } else { + // 中順走査における cur の次ノードを取得 + var tmp = cur.right + while (tmp!!.left != null) { + tmp = tmp.left + } + // ノード tmp を再帰的に削除 + remove(tmp._val) + // tmp で cur を上書きする + cur._val = tmp._val + } + } +} + +/* Driver Code */ +fun main() { + /* 二分探索木を初期化 */ + val bst = BinarySearchTree() + // 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる + val nums = intArrayOf(8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15) + for (num in nums) { + bst.insert(num) + } + println("\n初期化した二分木は\n") + printTree(bst.getRoot()) + + /* ノードを探索 */ + val node = bst.search(7) + println("見つかったノードオブジェクトは $node、ノードの値 = ${node?._val}") + + /* ノードを挿入 */ + bst.insert(16) + println("\nノード 16 を挿入後、二分木は\n") + printTree(bst.getRoot()) + + /* ノードを削除 */ + bst.remove(1) + println("\nノード 1 を削除後、二分木は\n") + printTree(bst.getRoot()) + bst.remove(2) + println("\nノード 2 を削除後、二分木は\n") + printTree(bst.getRoot()) + bst.remove(4) + println("\nノード 4 を削除後、二分木は\n") + printTree(bst.getRoot()) +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_tree/binary_tree.kt b/ja/codes/kotlin/chapter_tree/binary_tree.kt new file mode 100644 index 000000000..133bc0aa4 --- /dev/null +++ b/ja/codes/kotlin/chapter_tree/binary_tree.kt @@ -0,0 +1,40 @@ +/** + * File: binary_tree.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree + +/* Driver Code */ +fun main() { + /* 二分木を初期化 */ + // ノードを初期化 + val n1 = TreeNode(1) + val n2 = TreeNode(2) + val n3 = TreeNode(3) + val n4 = TreeNode(4) + val n5 = TreeNode(5) + // ノード間の参照(ポインタ)を構築する + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + println("\n二分木を初期化\n") + printTree(n1) + + /* ノードの挿入と削除 */ + val P = TreeNode(0) + // n1 -> n2 の間にノード P を挿入 + n1.left = P + P.left = n2 + println("\nノード P を挿入後\n") + printTree(n1) + // ノード P を削除 + n1.left = n2 + println("\nノード P を削除後\n") + printTree(n1) +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_tree/binary_tree_bfs.kt b/ja/codes/kotlin/chapter_tree/binary_tree_bfs.kt new file mode 100644 index 000000000..212bef50c --- /dev/null +++ b/ja/codes/kotlin/chapter_tree/binary_tree_bfs.kt @@ -0,0 +1,42 @@ +/** + * File: binary_tree_bfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree +import java.util.* + +/* レベル順走査 */ +fun levelOrder(root: TreeNode?): MutableList { + // キューを初期化し、ルートノードを追加する + val queue = LinkedList() + queue.add(root) + // 走査順序を保存するためのリストを初期化する + val list = mutableListOf() + while (queue.isNotEmpty()) { + val node = queue.poll() // デキュー + list.add(node?._val!!) // ノードの値を保存する + if (node.left != null) + queue.offer(node.left) // 左子ノードをキューに追加 + if (node.right != null) + queue.offer(node.right) // 右子ノードをキューに追加 + } + return list +} + +/* Driver Code */ +fun main() { + /* 二分木を初期化 */ + // ここではリストから直接二分木を生成する関数を利用する + val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7)) + println("\n二分木を初期化\n") + printTree(root) + + /* レベル順走査 */ + val list = levelOrder(root) + println("\nレベル順走査のノード出力順 = $list") +} \ No newline at end of file diff --git a/ja/codes/kotlin/chapter_tree/binary_tree_dfs.kt b/ja/codes/kotlin/chapter_tree/binary_tree_dfs.kt new file mode 100644 index 000000000..d722543dd --- /dev/null +++ b/ja/codes/kotlin/chapter_tree/binary_tree_dfs.kt @@ -0,0 +1,64 @@ +/** + * File: binary_tree_dfs.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package chapter_tree + +import utils.TreeNode +import utils.printTree + +// 走査順序を格納するリストを初期化 +var list = mutableListOf() + +/* 先行順走査 */ +fun preOrder(root: TreeNode?) { + if (root == null) return + // 訪問順序:根ノード -> 左部分木 -> 右部分木 + list.add(root._val) + preOrder(root.left) + preOrder(root.right) +} + +/* 中順走査 */ +fun inOrder(root: TreeNode?) { + if (root == null) return + // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 + inOrder(root.left) + list.add(root._val) + inOrder(root.right) +} + +/* 後順走査 */ +fun postOrder(root: TreeNode?) { + if (root == null) return + // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード + postOrder(root.left) + postOrder(root.right) + list.add(root._val) +} + +/* Driver Code */ +fun main() { + /* 二分木を初期化 */ + // ここではリストから直接二分木を生成する関数を利用する + val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7)) + println("\n二分木を初期化\n") + printTree(root) + + /* 先行順走査 */ + list.clear() + preOrder(root) + println("\n先行順走査のノード出力順 = $list") + + /* 中順走査 */ + list.clear() + inOrder(root) + println("\n中間順走査のノード出力順 = $list") + + /* 後順走査 */ + list.clear() + postOrder(root) + println("\n後行順走査のノード出力順 = $list") +} \ No newline at end of file diff --git a/ja/codes/kotlin/utils/ListNode.kt b/ja/codes/kotlin/utils/ListNode.kt new file mode 100644 index 000000000..116d3d64a --- /dev/null +++ b/ja/codes/kotlin/utils/ListNode.kt @@ -0,0 +1,25 @@ +/** + * File: ListNode.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package utils + +/* 連結リストノード */ +class ListNode(var _val: Int) { + var next: ListNode? = null + + companion object { + /* リストを連結リストにデシリアライズする */ + fun arrToLinkedList(arr: IntArray): ListNode? { + val dum = ListNode(0) + var head = dum + for (_val in arr) { + head.next = ListNode(_val) + head = head.next!! + } + return dum.next + } + } +} \ No newline at end of file diff --git a/ja/codes/kotlin/utils/PrintUtil.kt b/ja/codes/kotlin/utils/PrintUtil.kt new file mode 100644 index 000000000..42114d105 --- /dev/null +++ b/ja/codes/kotlin/utils/PrintUtil.kt @@ -0,0 +1,107 @@ +/** + * File: PrintUtil.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package utils + +import java.util.* + +class Trunk(var prev: Trunk?, var str: String) + +/* 行列を出力する(Array) */ +fun printMatrix(matrix: Array>) { + println("[") + for (row in matrix) { + println(" $row,") + } + println("]") +} + +/* 行列を出力する(List) */ +fun printMatrix(matrix: MutableList>) { + println("[") + for (row in matrix) { + println(" $row,") + } + println("]") +} + +/* 連結リストを出力 */ +fun printLinkedList(h: ListNode?) { + var head = h + val list = mutableListOf() + while (head != null) { + list.add(head._val.toString()) + head = head.next + } + println(list.joinToString(separator = " -> ")) +} + +/* 二分木を出力 */ +fun printTree(root: TreeNode?) { + printTree(root, null, false) +} + +/** + * 二分木を出力 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +fun printTree(root: TreeNode?, prev: Trunk?, isRight: Boolean) { + if (root == null) { + return + } + + var prevStr = " " + val trunk = Trunk(prev, prevStr) + + printTree(root.right, trunk, true) + + if (prev == null) { + trunk.str = "———" + } else if (isRight) { + trunk.str = "/———" + prevStr = " |" + } else { + trunk.str = "\\———" + prev.str = prevStr + } + + showTrunks(trunk) + println(" ${root._val}") + + if (prev != null) { + prev.str = prevStr + } + trunk.str = " |" + + printTree(root.left, trunk, false) +} + +fun showTrunks(p: Trunk?) { + if (p == null) { + return + } + showTrunks(p.prev) + print(p.str) +} + +/* ハッシュテーブルを出力 */ +fun printHashMap(map: Map) { + for ((key, value) in map) { + println("${key.toString()} -> $value") + } +} + +/* ヒープを出力 */ +fun printHeap(queue: Queue?) { + val list = mutableListOf() + queue?.let { list.addAll(it) } + print("ヒープの配列表現:") + println(list) + println("ヒープの木構造表現:") + val root = TreeNode.listToTree(list) + printTree(root) +} \ No newline at end of file diff --git a/ja/codes/kotlin/utils/TreeNode.kt b/ja/codes/kotlin/utils/TreeNode.kt new file mode 100644 index 000000000..8a413e284 --- /dev/null +++ b/ja/codes/kotlin/utils/TreeNode.kt @@ -0,0 +1,69 @@ +/** + * File: TreeNode.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package utils + +/* 二分木ノードクラス */ +/* コンストラクタ */ +class TreeNode( + var _val: Int // ノード値 +) { + var height: Int = 0 // ノードの高さ + var left: TreeNode? = null // 左子ノードへの参照 + var right: TreeNode? = null // 右子ノードへの参照 + + // シリアライズの符号化規則は以下を参照: + // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + // 二分木の配列表現: + // [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] + // 二分木の連結リスト表現: + // /——— 15 + // /——— 7 + // /——— 3 + // | \——— 6 + // | \——— 12 + // ——— 1 + // \——— 2 + // | /——— 9 + // \——— 4 + // \——— 8 + + /* リストを二分木にデシリアライズする: 再帰 */ + companion object { + private fun listToTreeDFS(arr: MutableList, i: Int): TreeNode? { + if (i < 0 || i >= arr.size || arr[i] == null) { + return null + } + val root = TreeNode(arr[i]!!) + root.left = listToTreeDFS(arr, 2 * i + 1) + root.right = listToTreeDFS(arr, 2 * i + 2) + return root + } + + /* リストを二分木にデシリアライズする */ + fun listToTree(arr: MutableList): TreeNode? { + return listToTreeDFS(arr, 0) + } + + /* 二分木をリストにシリアライズする: 再帰 */ + private fun treeToListDFS(root: TreeNode?, i: Int, res: MutableList) { + if (root == null) return + while (i >= res.size) { + res.add(null) + } + res[i] = root._val + treeToListDFS(root.left, 2 * i + 1, res) + treeToListDFS(root.right, 2 * i + 2, res) + } + + /* 二分木をリストにシリアライズする */ + fun treeToList(root: TreeNode?): MutableList { + val res = mutableListOf() + treeToListDFS(root, 0, res) + return res + } + } +} \ No newline at end of file diff --git a/ja/codes/kotlin/utils/Vertex.kt b/ja/codes/kotlin/utils/Vertex.kt new file mode 100644 index 000000000..6bd84f51a --- /dev/null +++ b/ja/codes/kotlin/utils/Vertex.kt @@ -0,0 +1,30 @@ +/** + * File: Vertex.kt + * Created Time: 2024-01-25 + * Author: curtishd (1023632660@qq.com) + */ + +package utils + +/* 頂点クラス */ +class Vertex(val _val: Int) { + companion object { + /* 値リスト vals を入力し、頂点リスト vets を返す */ + fun valsToVets(vals: IntArray): Array { + val vets = arrayOfNulls(vals.size) + for (i in vals.indices) { + vets[i] = Vertex(vals[i]) + } + return vets + } + + /* 頂点リスト vets を入力し、値リスト vals を返す */ + fun vetsToVals(vets: MutableList): MutableList { + val vals = mutableListOf() + for (vet in vets) { + vals.add(vet!!._val) + } + return vals + } + } +} \ No newline at end of file diff --git a/ja/codes/python/.gitignore b/ja/codes/python/.gitignore new file mode 100644 index 000000000..bee8a64b7 --- /dev/null +++ b/ja/codes/python/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/ja/codes/python/chapter_array_and_linkedlist/array.py b/ja/codes/python/chapter_array_and_linkedlist/array.py index 437324f8f..a1e33d1dd 100644 --- a/ja/codes/python/chapter_array_and_linkedlist/array.py +++ b/ja/codes/python/chapter_array_and_linkedlist/array.py @@ -8,21 +8,21 @@ import random def random_access(nums: list[int]) -> int: - """要素へのランダムアクセス""" - # 区間 [0, len(nums)-1] から数値をランダムに選択 + """要素へランダムアクセス""" + # 区間 [0, len(nums)-1] からランダムに数字を 1 つ選ぶ random_index = random.randint(0, len(nums) - 1) # ランダムな要素を取得して返す random_num = nums[random_index] return random_num -# PythonのlistはextendできるDynamic Arrayであることに注意 -# 学習を容易にするため、この関数ではlistをStatic Arrayとして扱う +# Python の list は動的配列であり、直接拡張できます +# 学習しやすいよう、本関数では list を長さ不変の配列として扱います def extend(nums: list[int], enlarge: int) -> list[int]: - """配列の長さを拡張""" - # 拡張された長さの配列を初期化 + """配列長を拡張する""" + # 拡張後の長さを持つ配列を初期化する res = [0] * (len(nums) + enlarge) - # 元の配列のすべての要素を新しい配列にコピー + # 元の配列の全要素を新しい配列にコピー for i in range(len(nums)): res[i] = nums[i] # 拡張後の新しい配列を返す @@ -30,38 +30,38 @@ def extend(nums: list[int], enlarge: int) -> list[int]: def insert(nums: list[int], num: int, index: int): - """インデックス index に要素 num を挿入""" - # インデックス index より後のすべての要素を1つ後ろに移動 + """配列の index 番目に要素 num を挿入""" + # インデックス index 以降の全要素を 1 つ後ろへ移動する for i in range(len(nums) - 1, index, -1): nums[i] = nums[i - 1] - # num を index の位置の要素に代入 + # index の要素に num を代入する nums[index] = num def remove(nums: list[int], index: int): - """インデックス index の要素を削除""" - # インデックス index より後のすべての要素を1つ前に移動 + """index の要素を削除する""" + # インデックス index より後ろの全要素を 1 つ前へ移動する for i in range(index, len(nums) - 1): nums[i] = nums[i + 1] def traverse(nums: list[int]): - """配列の走査""" + """配列を走査""" count = 0 - # インデックスによる配列の走査 + # インデックスで配列を走査 for i in range(len(nums)): count += nums[i] - # 配列要素の走査 + # 配列要素を直接走査 for num in nums: count += num - # データのインデックスと要素の両方を走査 + # データのインデックスと要素を同時に走査する for i, num in enumerate(nums): count += nums[i] count += num def find(nums: list[int], target: int) -> int: - """配列内の指定された要素を検索""" + """配列内で指定要素を探す""" for i in range(len(nums)): if nums[i] == target: return i @@ -78,23 +78,23 @@ if __name__ == "__main__": # ランダムアクセス random_num: int = random_access(nums) - print("nums のランダムな要素を取得", random_num) + print("nums からランダムな要素を取得", random_num) - # 長さの拡張 + # 長さを拡張 nums: list[int] = extend(nums, 3) - print("配列の長さを 8 に拡張、結果は nums =", nums) + print("配列の長さを 8 に拡張し、nums =", nums) - # 要素の挿入 + # 要素を挿入する insert(nums, 6, 3) - print("インデックス 3 に数値 6 を挿入、結果は nums =", nums) + print("インデックス 3 に数値 6 を挿入し、nums =", nums) - # 要素の削除 + # 要素を削除 remove(nums, 2) - print("インデックス 2 の要素を削除、結果は nums =", nums) + print("インデックス 2 の要素を削除し、nums =", nums) - # 配列の走査 + # 配列を走査 traverse(nums) - # 要素の検索 + # 要素を探索する index: int = find(nums, 3) - print("nums で要素 3 を検索、結果は index =", index) \ No newline at end of file + print("nums で要素 3 を検索し、インデックス =", index) diff --git a/ja/codes/python/chapter_array_and_linkedlist/linked_list.py b/ja/codes/python/chapter_array_and_linkedlist/linked_list.py index 82b44d851..3501f2088 100644 --- a/ja/codes/python/chapter_array_and_linkedlist/linked_list.py +++ b/ja/codes/python/chapter_array_and_linkedlist/linked_list.py @@ -12,14 +12,14 @@ from modules import ListNode, print_linked_list def insert(n0: ListNode, P: ListNode): - """連結リストのノード n0 の後にノード P を挿入""" + """連結リストでノード n0 の後ろにノード P を挿入する""" n1 = n0.next P.next = n1 n0.next = P def remove(n0: ListNode): - """連結リストのノード n0 の後の最初のノードを削除""" + """連結リストでノード n0 の直後のノードを削除する""" if not n0.next: return # n0 -> P -> n1 @@ -29,7 +29,7 @@ def remove(n0: ListNode): def access(head: ListNode, index: int) -> ListNode | None: - """連結リストのインデックス index のノードにアクセス""" + """連結リスト内で index 番目のノードにアクセス""" for _ in range(index): if not head: return None @@ -38,7 +38,7 @@ def access(head: ListNode, index: int) -> ListNode | None: def find(head: ListNode, target: int) -> int: - """連結リストで値 target を持つ最初のノードを検索""" + """連結リストで値が target の最初のノードを探す""" index = 0 while head: if head.val == target: @@ -50,36 +50,36 @@ def find(head: ListNode, target: int) -> int: """Driver Code""" if __name__ == "__main__": - # 連結リストを初期化 - # 各ノードを初期化 + # 連結リストを初期化する + # 各ノードを初期化する n0 = ListNode(1) n1 = ListNode(3) n2 = ListNode(2) n3 = ListNode(5) n4 = ListNode(4) - # ノード間の参照を構築 + # ノード間の参照を構築する n0.next = n1 n1.next = n2 n2.next = n3 n3.next = n4 - print("初期化された連結リスト") + print("初期化した連結リストは") print_linked_list(n0) # ノードを挿入 p = ListNode(0) insert(n0, p) - print("ノード挿入後の連結リスト") + print("ノード挿入後の連結リストは") print_linked_list(n0) # ノードを削除 remove(n0) - print("ノード削除後の連結リスト") + print("ノード削除後の連結リストは") print_linked_list(n0) # ノードにアクセス node: ListNode = access(n0, 3) print("連結リストのインデックス 3 のノードの値 = {}".format(node.val)) - # ノードを検索 + # ノードを探索 index: int = find(n0, 2) - print("連結リストで値 2 を持つノードのインデックス = {}".format(index)) \ No newline at end of file + print("連結リスト内で値が 2 のノードのインデックス = {}".format(index)) diff --git a/ja/codes/python/chapter_array_and_linkedlist/list.py b/ja/codes/python/chapter_array_and_linkedlist/list.py index af76de80f..779befaf2 100644 --- a/ja/codes/python/chapter_array_and_linkedlist/list.py +++ b/ja/codes/python/chapter_array_and_linkedlist/list.py @@ -12,15 +12,15 @@ if __name__ == "__main__": # 要素にアクセス x: int = nums[1] - print("\nインデックス 1 の要素にアクセス、結果は x =", x) + print("\nインデックス 1 の要素にアクセスし、x =", x) # 要素を更新 nums[1] = 0 - print("\nインデックス 1 の要素を 0 に更新、結果は nums =", nums) + print("\nインデックス 1 の要素を 0 に更新し、nums =", nums) - # リストをクリア + # リストを空にする nums.clear() - print("\nリストをクリア後、nums =", nums) + print("\nリストを空にした後 nums =", nums) # 末尾に要素を追加 nums.append(1) @@ -28,29 +28,29 @@ if __name__ == "__main__": nums.append(2) nums.append(5) nums.append(4) - print("\n要素を追加後、nums =", nums) + print("\n要素追加後 nums =", nums) # 中間に要素を挿入 nums.insert(3, 6) - print("\nインデックス 3 に数値 6 を挿入、結果は nums =", nums) + print("\nインデックス 3 に数値 6 を挿入すると、nums =", nums) # 要素を削除 nums.pop(3) - print("\nインデックス 3 の要素を削除、結果は nums =", nums) + print("\nインデックス 3 の要素を削除すると、nums =", nums) - # インデックスによるリストの走査 + # インデックスでリストを走査 count = 0 for i in range(len(nums)): count += nums[i] - # リスト要素の走査 + # リスト要素を直接走査 for num in nums: count += num - # 2つのリストを連結 + # 2 つのリストを連結する nums1 = [6, 8, 7, 10, 9] nums += nums1 - print("\nリスト nums1 を nums に連結、結果は nums =", nums) + print("\nリスト nums1 を nums の後ろに連結すると、nums =", nums) # リストをソート nums.sort() - print("\nリストをソート後、nums =", nums) \ No newline at end of file + print("\nリストをソートすると nums =", nums) diff --git a/ja/codes/python/chapter_array_and_linkedlist/my_list.py b/ja/codes/python/chapter_array_and_linkedlist/my_list.py index bca475647..399e4cdf5 100644 --- a/ja/codes/python/chapter_array_and_linkedlist/my_list.py +++ b/ja/codes/python/chapter_array_and_linkedlist/my_list.py @@ -10,35 +10,35 @@ class MyList: def __init__(self): """コンストラクタ""" - self._capacity: int = 10 # リストの容量 + self._capacity: int = 10 # リスト容量 self._arr: list[int] = [0] * self._capacity # 配列(リスト要素を格納) self._size: int = 0 # リストの長さ(現在の要素数) - self._extend_ratio: int = 2 # 各リスト拡張の倍数 + self._extend_ratio: int = 2 # リスト拡張時の増加倍率 def size(self) -> int: - """リストの長さ(現在の要素数)を取得""" + """リストの長さを取得(現在の要素数)""" return self._size def capacity(self) -> int: - """リストの容量を取得""" + """リスト容量を取得する""" return self._capacity def get(self, index: int) -> int: """要素にアクセス""" - # インデックスが範囲外の場合、以下のように例外をスロー + # インデックスが範囲外なら例外を送出する。以下同様 if index < 0 or index >= self._size: - raise IndexError("Index out of bounds") + raise IndexError("インデックスが範囲外です") return self._arr[index] def set(self, num: int, index: int): """要素を更新""" if index < 0 or index >= self._size: - raise IndexError("Index out of bounds") + raise IndexError("インデックスが範囲外です") self._arr[index] = num def add(self, num: int): """末尾に要素を追加""" - # 要素数が容量を超える場合、拡張メカニズムをトリガー + # 要素数が容量を超えると、拡張機構が発動する if self.size() == self.capacity(): self.extend_capacity() self._arr[self._size] = num @@ -47,11 +47,11 @@ class MyList: def insert(self, num: int, index: int): """中間に要素を挿入""" if index < 0 or index >= self._size: - raise IndexError("Index out of bounds") - # 要素数が容量を超える場合、拡張メカニズムをトリガー + raise IndexError("インデックスが範囲外です") + # 要素数が容量を超えると、拡張機構が発動する if self._size == self.capacity(): self.extend_capacity() - # インデックス index より後のすべての要素を1つ後ろに移動 + # index 以降の要素をすべて 1 つ後ろへずらす for j in range(self._size - 1, index - 1, -1): self._arr[j + 1] = self._arr[j] self._arr[index] = num @@ -61,9 +61,9 @@ class MyList: def remove(self, index: int) -> int: """要素を削除""" if index < 0 or index >= self._size: - raise IndexError("Index out of bounds") + raise IndexError("インデックスが範囲外です") num = self._arr[index] - # インデックス index より後のすべての要素を1つ前に移動 + # インデックス index より後の要素をすべて 1 つ前に移動する for j in range(index, self._size - 1): self._arr[j] = self._arr[j + 1] # 要素数を更新 @@ -72,14 +72,14 @@ class MyList: return num def extend_capacity(self): - """リストを拡張""" - # 元の配列の _extend_ratio 倍の長さの新しい配列を作成し、元の配列を新しい配列にコピー + """リストの拡張""" + # 元の配列の `_extend_ratio` 倍の長さを持つ新しい配列を作成し、元の配列を新しい配列にコピーする self._arr = self._arr + [0] * self.capacity() * (self._extend_ratio - 1) # リストの容量を更新 self._capacity = len(self._arr) def to_array(self) -> list[int]: - """有効な長さのリストを返す""" + """有効長のリストを返す""" return self._arr[: self._size] @@ -93,26 +93,26 @@ if __name__ == "__main__": nums.add(2) nums.add(5) nums.add(4) - print(f"リスト nums = {nums.to_array()} ,容量 = {nums.capacity()} ,長さ = {nums.size()}") + print(f"リスト nums = {nums.to_array()} 、容量 = {nums.capacity()} 、長さ = {nums.size()}") # 中間に要素を挿入 nums.insert(6, index=3) - print("インデックス 3 に数値 6 を挿入、結果は nums =", nums.to_array()) + print("インデックス 3 に数値 6 を挿入すると、nums =", nums.to_array()) # 要素を削除 nums.remove(3) - print("インデックス 3 の要素を削除、結果は nums =", nums.to_array()) + print("インデックス 3 の要素を削除すると、nums =", nums.to_array()) # 要素にアクセス num = nums.get(1) - print("インデックス 1 の要素にアクセス、結果は num =", num) + print("インデックス 1 の要素にアクセスすると、num =", num) # 要素を更新 nums.set(0, 1) - print("インデックス 1 の要素を 0 に更新、結果は nums =", nums.to_array()) + print("インデックス 1 の要素を 0 に更新すると、nums =", nums.to_array()) - # 拡張メカニズムのテスト + # 拡張機構をテストする for i in range(10): - # i = 5 のとき、リストの長さがリストの容量を超え、この時点で拡張メカニズムがトリガーされる + # i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する nums.add(i) - print(f"拡張後、リスト {nums.to_array()} ,容量 = {nums.capacity()} ,長さ = {nums.size()}") \ No newline at end of file + print(f"拡張後のリスト {nums.to_array()} 、容量 = {nums.capacity()} 、長さ = {nums.size()}") diff --git a/ja/codes/python/chapter_backtracking/n_queens.py b/ja/codes/python/chapter_backtracking/n_queens.py index 8fa039dd8..285240e95 100644 --- a/ja/codes/python/chapter_backtracking/n_queens.py +++ b/ja/codes/python/chapter_backtracking/n_queens.py @@ -14,49 +14,49 @@ def backtrack( diags1: list[bool], diags2: list[bool], ): - """バックトラッキングアルゴリズム:n クイーン""" - # すべての行が配置されたら、解を記録 + """バックトラッキング:N クイーン""" + # すべての行への配置が完了したら、解を記録する if row == n: res.append([list(row) for row in state]) return # すべての列を走査 for col in range(n): - # セルに対応する主対角線と副対角線を計算 + # このマスに対応する主対角線と副対角線を計算 diag1 = row - col + n - 1 diag2 = row + col - # 枝刈り:セルの列、主対角線、副対角線にクイーンを配置しない + # 枝刈り:そのマスの列、主対角線、副対角線にクイーンがあってはならない if not cols[col] and not diags1[diag1] and not 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 def n_queens(n: int) -> list[list[list[str]]]: - """n クイーンを解く""" - # n*n サイズのチェスボードを初期化、'Q' はクイーンを表し、'#' は空のスポットを表す + """N クイーンを解く""" + # n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す state = [["#" for _ in range(n)] for _ in range(n)] - cols = [False] * n # クイーンがある列を記録 - diags1 = [False] * (2 * n - 1) # クイーンがある主対角線を記録 - diags2 = [False] * (2 * n - 1) # クイーンがある副対角線を記録 + cols = [False] * n # 列にクイーンがあるか記録 + diags1 = [False] * (2 * n - 1) # 主対角線にクイーンがあるかを記録 + diags2 = [False] * (2 * n - 1) # 副対角線にクイーンがあるかを記録 res = [] backtrack(0, n, state, res, cols, diags1, diags2) return res -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": n = 4 res = n_queens(n) - print(f"チェスボードの寸法入力:{n}") - print(f"クイーン配置解の総数は {len(res)}") + print(f"入力された盤面の縦横の長さは {n} です") + print(f"クイーンの配置パターンは全部で {len(res)} 通りです") for state in res: print("--------------------") for row in state: - print(row) \ No newline at end of file + print(row) diff --git a/ja/codes/python/chapter_backtracking/permutations_i.py b/ja/codes/python/chapter_backtracking/permutations_i.py index 6b7abf08d..11aa43fc7 100644 --- a/ja/codes/python/chapter_backtracking/permutations_i.py +++ b/ja/codes/python/chapter_backtracking/permutations_i.py @@ -8,8 +8,8 @@ Author: krahets (krahets@163.com) def backtrack( state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] ): - """バックトラッキングアルゴリズム:順列 I""" - # 状態の長さが要素数と等しいとき、解を記録 + """バックトラッキング:順列 I""" + # 状態の長さが要素数に等しければ、解を記録 if len(state) == len(choices): res.append(list(state)) return @@ -17,28 +17,28 @@ def backtrack( for i, choice in enumerate(choices): # 枝刈り:要素の重複選択を許可しない if not selected[i]: - # 試行:選択を行い、状態を更新 + # 試行: 選択を行い、状態を更新 selected[i] = True state.append(choice) - # 次の選択ラウンドに進む + # 次の選択へ進む backtrack(state, choices, selected, res) - # 撤回:選択を取り消し、前の状態に復元 + # バックトラック:選択を取り消し、前の状態に戻す selected[i] = False state.pop() def permutations_i(nums: list[int]) -> list[list[int]]: - """順列 I""" + """全順列 I""" res = [] backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) return res -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": nums = [1, 2, 3] res = permutations_i(nums) print(f"入力配列 nums = {nums}") - print(f"すべての順列 res = {res}") \ No newline at end of file + print(f"すべての順列 res = {res}") diff --git a/ja/codes/python/chapter_backtracking/permutations_ii.py b/ja/codes/python/chapter_backtracking/permutations_ii.py index 4f758f57c..71cc221d8 100644 --- a/ja/codes/python/chapter_backtracking/permutations_ii.py +++ b/ja/codes/python/chapter_backtracking/permutations_ii.py @@ -8,39 +8,39 @@ Author: krahets (krahets@163.com) def backtrack( state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] ): - """バックトラッキングアルゴリズム:順列 II""" - # 状態の長さが要素数と等しいとき、解を記録 + """バックトラッキング:順列 II""" + # 状態の長さが要素数に等しければ、解を記録 if len(state) == len(choices): res.append(list(state)) return # すべての選択肢を走査 duplicated = set[int]() for i, choice in enumerate(choices): - # 枝刈り:要素の重複選択を許可せず、等しい要素の重複選択も許可しない + # 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない if not selected[i] and choice not in duplicated: - # 試行:選択を行い、状態を更新 - duplicated.add(choice) # 選択された要素値を記録 + # 試行: 選択を行い、状態を更新 + duplicated.add(choice) # 選択済みの要素値を記録 selected[i] = True state.append(choice) - # 次の選択ラウンドに進む + # 次の選択へ進む backtrack(state, choices, selected, res) - # 撤回:選択を取り消し、前の状態に復元 + # バックトラック:選択を取り消し、前の状態に戻す selected[i] = False state.pop() def permutations_ii(nums: list[int]) -> list[list[int]]: - """順列 II""" + """全順列 II""" res = [] backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) return res -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": nums = [1, 2, 2] res = permutations_ii(nums) print(f"入力配列 nums = {nums}") - print(f"すべての順列 res = {res}") \ No newline at end of file + print(f"すべての順列 res = {res}") diff --git a/ja/codes/python/chapter_backtracking/preorder_traversal_i_compact.py b/ja/codes/python/chapter_backtracking/preorder_traversal_i_compact.py index a9316158e..1caf499e3 100644 --- a/ja/codes/python/chapter_backtracking/preorder_traversal_i_compact.py +++ b/ja/codes/python/chapter_backtracking/preorder_traversal_i_compact.py @@ -12,7 +12,7 @@ from modules import TreeNode, print_tree, list_to_tree def pre_order(root: TreeNode): - """前順走査:例一""" + """前順走査:例題 1""" if root is None: return if root.val == 7: @@ -22,15 +22,15 @@ def pre_order(root: TreeNode): pre_order(root.right) -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\n二分木を初期化") print_tree(root) - # 前順走査 + # 先行順走査 res = list[TreeNode]() pre_order(root) - print("\n値が 7 のすべてのノードを出力") - print([node.val for node in res]) \ No newline at end of file + print("\n値が 7 のノードをすべて出力") + print([node.val for node in res]) diff --git a/ja/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py b/ja/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py index fc8af7640..30d74255e 100644 --- a/ja/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py +++ b/ja/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py @@ -12,31 +12,31 @@ from modules import TreeNode, print_tree, list_to_tree def pre_order(root: TreeNode): - """前順走査:例二""" + """前順走査:例題 2""" if root is None: return - # 試行 + # 試す path.append(root) if root.val == 7: # 解を記録 res.append(list(path)) pre_order(root.left) pre_order(root.right) - # 撤回 + # バックトラック path.pop() -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\n二分木を初期化") print_tree(root) - # 前順走査 + # 先行順走査 path = list[TreeNode]() res = list[list[TreeNode]]() pre_order(root) - print("\nルートからノード 7 へのすべてのパスを出力") + print("\n根ノードからノード 7 までの経路をすべて出力") for path in res: - print([node.val for node in path]) \ No newline at end of file + print([node.val for node in path]) diff --git a/ja/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py b/ja/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py index f92a30e98..41f219cc1 100644 --- a/ja/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py +++ b/ja/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py @@ -12,32 +12,32 @@ from modules import TreeNode, print_tree, list_to_tree def pre_order(root: TreeNode): - """前順走査:例三""" + """前順走査:例題 3""" # 枝刈り if root is None or root.val == 3: return - # 試行 + # 試す path.append(root) if root.val == 7: # 解を記録 res.append(list(path)) pre_order(root.left) pre_order(root.right) - # 撤回 + # バックトラック path.pop() -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\n二分木を初期化") print_tree(root) - # 前順走査 + # 先行順走査 path = list[TreeNode]() res = list[list[TreeNode]]() pre_order(root) - print("\nルートからノード 7 へのすべてのパスを出力、値が 3 のノードは含まない") + print("\n根ノードからノード 7 までの経路をすべて出力し、経路には値が 3 のノードを含めない") for path in res: - print([node.val for node in path]) \ No newline at end of file + print([node.val for node in path]) diff --git a/ja/codes/python/chapter_backtracking/preorder_traversal_iii_template.py b/ja/codes/python/chapter_backtracking/preorder_traversal_iii_template.py index a3e157e0b..f278ecbb7 100644 --- a/ja/codes/python/chapter_backtracking/preorder_traversal_iii_template.py +++ b/ja/codes/python/chapter_backtracking/preorder_traversal_iii_template.py @@ -22,7 +22,7 @@ def record_solution(state: list[TreeNode], res: list[list[TreeNode]]): def is_valid(state: list[TreeNode], choice: TreeNode) -> bool: - """現在の状態下で選択が合法かどうかを判定""" + """現在の状態で、この選択が有効かどうかを判定""" return choice is not None and choice.val != 3 @@ -32,40 +32,40 @@ def make_choice(state: list[TreeNode], choice: TreeNode): def undo_choice(state: list[TreeNode], choice: TreeNode): - """状態を復元""" + """状態を元に戻す""" state.pop() def backtrack( state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]] ): - """バックトラッキングアルゴリズム:例三""" - # 解かどうかをチェック + """バックトラッキング:例題 3""" + # 解かどうかを確認 if is_solution(state): # 解を記録 record_solution(state, res) # すべての選択肢を走査 for choice in choices: - # 枝刈り:選択が合法かどうかをチェック + # 枝刈り:選択が妥当かを確認する if is_valid(state, choice): - # 試行:選択を行い、状態を更新 + # 試行: 選択を行い、状態を更新 make_choice(state, choice) - # 次の選択ラウンドに進む + # 次の選択へ進む backtrack(state, [choice.left, choice.right], res) - # 撤回:選択を取り消し、前の状態に復元 + # バックトラック:選択を取り消し、前の状態に戻す undo_choice(state, choice) -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) print("\n二分木を初期化") print_tree(root) - # バックトラッキングアルゴリズム + # バックトラッキング法 res = [] backtrack(state=[], choices=[root], res=res) - print("\nルートからノード 7 へのすべてのパスを出力、パスに値が 3 のノードを含まないことを要求") + print("\n根ノードからノード 7 までの経路をすべて出力し、経路には値が 3 のノードを含まないことを条件とする") for path in res: - print([node.val for node in path]) \ No newline at end of file + print([node.val for node in path]) diff --git a/ja/codes/python/chapter_backtracking/subset_sum_i.py b/ja/codes/python/chapter_backtracking/subset_sum_i.py index 4683d3bc7..960ea8aab 100644 --- a/ja/codes/python/chapter_backtracking/subset_sum_i.py +++ b/ja/codes/python/chapter_backtracking/subset_sum_i.py @@ -8,41 +8,41 @@ Author: krahets (krahets@163.com) def backtrack( state: list[int], target: int, choices: list[int], start: int, res: list[list[int]] ): - """バックトラッキングアルゴリズム:部分集合の和 I""" - # 部分集合の和が target と等しいとき、解を記録 + """バックトラッキング:部分和 I""" + # 部分集合の和が target に等しければ、解を記録 if target == 0: res.append(list(state)) return # すべての選択肢を走査 - # 枝刈り二:start から走査を開始して重複する部分集合の生成を避ける + # 枝刈り 2: start から走査し、重複する部分集合の生成を避ける for i in range(start, len(choices)): - # 枝刈り一:部分集合の和が target を超える場合、直ちにループを終了 - # これは配列がソートされており、後の要素がより大きいため、部分集合の和は必ず target を超えるため + # 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + # 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if target - choices[i] < 0: break - # 試行:選択を行い、target、start を更新 + # 試す:選択を行い、target と start を更新 state.append(choices[i]) - # 次の選択ラウンドに進む + # 次の選択へ進む backtrack(state, target - choices[i], choices, i, res) - # 撤回:選択を取り消し、前の状態に復元 + # バックトラック:選択を取り消し、前の状態に戻す state.pop() def subset_sum_i(nums: list[int], target: int) -> list[list[int]]: - """部分集合の和 I を解く""" + """部分和 I を解く""" state = [] # 状態(部分集合) nums.sort() # nums をソート - start = 0 # 走査の開始点 - res = [] # 結果リスト(部分集合リスト) + start = 0 # 開始点を走査 + res = [] # 結果リスト(部分集合のリスト) backtrack(state, target, nums, start, res) return res -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": nums = [3, 4, 5] target = 9 res = subset_sum_i(nums, target) print(f"入力配列 nums = {nums}, target = {target}") - print(f"{target} と等しいすべての部分集合 res = {res}") \ No newline at end of file + print(f"和が {target} に等しいすべての部分集合 res = {res}") diff --git a/ja/codes/python/chapter_backtracking/subset_sum_i_naive.py b/ja/codes/python/chapter_backtracking/subset_sum_i_naive.py index 380867433..23ef7805f 100644 --- a/ja/codes/python/chapter_backtracking/subset_sum_i_naive.py +++ b/ja/codes/python/chapter_backtracking/subset_sum_i_naive.py @@ -12,39 +12,39 @@ def backtrack( choices: list[int], res: list[list[int]], ): - """バックトラッキングアルゴリズム:部分集合の和 I""" - # 部分集合の和が target と等しいとき、解を記録 + """バックトラッキング:部分和 I""" + # 部分集合の和が target に等しければ、解を記録 if total == target: res.append(list(state)) return # すべての選択肢を走査 for i in range(len(choices)): - # 枝刈り:部分集合の和が target を超える場合、その選択をスキップ + # 枝刈り:部分和が target を超える場合はその選択をスキップする if total + choices[i] > target: continue - # 試行:選択を行い、要素と total を更新 + # 試行:選択を行い、要素と total を更新する state.append(choices[i]) - # 次の選択ラウンドに進む + # 次の選択へ進む backtrack(state, target, total + choices[i], choices, res) - # 撤回:選択を取り消し、前の状態に復元 + # バックトラック:選択を取り消し、前の状態に戻す state.pop() def subset_sum_i_naive(nums: list[int], target: int) -> list[list[int]]: - """部分集合の和 I を解く(重複する部分集合を含む)""" + """部分和 I を解く(重複部分集合を含む)""" state = [] # 状態(部分集合) - total = 0 # 部分集合の和 - res = [] # 結果リスト(部分集合リスト) + total = 0 # 部分和 + res = [] # 結果リスト(部分集合のリスト) backtrack(state, target, total, nums, res) return res -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": nums = [3, 4, 5] target = 9 res = subset_sum_i_naive(nums, target) print(f"入力配列 nums = {nums}, target = {target}") - print(f"{target} と等しいすべての部分集合 res = {res}") - print(f"この方法の結果には重複する集合が含まれる") \ No newline at end of file + print(f"和が {target} に等しいすべての部分集合 res = {res}") + print(f"注意: この方法の出力結果には重複する集合が含まれます") diff --git a/ja/codes/python/chapter_backtracking/subset_sum_ii.py b/ja/codes/python/chapter_backtracking/subset_sum_ii.py index aeac0fc94..ab3db00d2 100644 --- a/ja/codes/python/chapter_backtracking/subset_sum_ii.py +++ b/ja/codes/python/chapter_backtracking/subset_sum_ii.py @@ -8,45 +8,45 @@ Author: krahets (krahets@163.com) def backtrack( state: list[int], target: int, choices: list[int], start: int, res: list[list[int]] ): - """バックトラッキングアルゴリズム:部分集合の和 II""" - # 部分集合の和が target と等しいとき、解を記録 + """バックトラッキング:部分和 II""" + # 部分集合の和が target に等しければ、解を記録 if target == 0: res.append(list(state)) return # すべての選択肢を走査 - # 枝刈り二:start から走査を開始して重複する部分集合の生成を避ける - # 枝刈り三:start から走査を開始して同じ要素の重複選択を避ける + # 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + # 枝刈り 3: start から走査し、同じ要素の重複選択を避ける for i in range(start, len(choices)): - # 枝刈り一:部分集合の和が target を超える場合、直ちにループを終了 - # これは配列がソートされており、後の要素がより大きいため、部分集合の和は必ず target を超えるため + # 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + # 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため if target - choices[i] < 0: break - # 枝刈り四:要素が左の要素と等しい場合、検索分岐が重複していることを示すため、スキップ + # 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする if i > start and choices[i] == choices[i - 1]: continue - # 試行:選択を行い、target、start を更新 + # 試す:選択を行い、target と start を更新 state.append(choices[i]) - # 次の選択ラウンドに進む + # 次の選択へ進む backtrack(state, target - choices[i], choices, i + 1, res) - # 撤回:選択を取り消し、前の状態に復元 + # バックトラック:選択を取り消し、前の状態に戻す state.pop() def subset_sum_ii(nums: list[int], target: int) -> list[list[int]]: - """部分集合の和 II を解く""" + """部分和 II を解く""" state = [] # 状態(部分集合) nums.sort() # nums をソート - start = 0 # 走査の開始点 - res = [] # 結果リスト(部分集合リスト) + start = 0 # 開始点を走査 + res = [] # 結果リスト(部分集合のリスト) backtrack(state, target, nums, start, res) return res -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": nums = [4, 4, 5] target = 9 res = subset_sum_ii(nums, target) print(f"入力配列 nums = {nums}, target = {target}") - print(f"{target} と等しいすべての部分集合 res = {res}") \ No newline at end of file + print(f"和が {target} に等しいすべての部分集合 res = {res}") diff --git a/ja/codes/python/chapter_computational_complexity/iteration.py b/ja/codes/python/chapter_computational_complexity/iteration.py index b453664e3..b6d5952d3 100644 --- a/ja/codes/python/chapter_computational_complexity/iteration.py +++ b/ja/codes/python/chapter_computational_complexity/iteration.py @@ -6,44 +6,44 @@ Author: krahets (krahets@163.com) def for_loop(n: int) -> int: - """forループ""" + """for ループ""" res = 0 - # 1, 2, ..., n-1, n の合計をループ + # 1, 2, ..., n-1, n を順に加算する for i in range(1, n + 1): res += i return res def while_loop(n: int) -> int: - """whileループ""" + """while ループ""" res = 0 - i = 1 # 条件変数を初期化 - # 1, 2, ..., n-1, n の合計をループ + i = 1 # 条件変数を初期化する + # 1, 2, ..., n-1, n を順に加算する while i <= n: res += i - i += 1 # 条件変数を更新 + i += 1 # 条件変数を更新する return res def while_loop_ii(n: int) -> int: - """whileループ(2つの更新)""" + """while ループ(2回更新)""" res = 0 - i = 1 # 条件変数を初期化 - # 1, 4, 10, ... の合計をループ + i = 1 # 条件変数を初期化する + # 1, 4, 10, ... を順に加算する while i <= n: res += i - # 条件変数を更新 + # 条件変数を更新する i += 1 i *= 2 return res def nested_for_loop(n: int) -> str: - """二重forループ""" + """二重 for ループ""" res = "" - # i = 1, 2, ..., n-1, n をループ + # i = 1, 2, ..., n-1, n とループする for i in range(1, n + 1): - # j = 1, 2, ..., n-1, n をループ + # j = 1, 2, ..., n-1, n とループする for j in range(1, n + 1): res += f"({i}, {j}), " return res @@ -53,13 +53,13 @@ def nested_for_loop(n: int) -> str: if __name__ == "__main__": n = 5 res = for_loop(n) - print(f"\nforループの合計結果 res = {res}") + print(f"\nfor ループの合計結果 res = {res}") res = while_loop(n) - print(f"\nwhileループの合計結果 res = {res}") + print(f"\nwhile ループの合計結果 res = {res}") res = while_loop_ii(n) - print(f"\nwhileループ(2つの更新)の合計結果 res = {res}") + print(f"\nwhile ループ(2 回更新)の合計結果 res = {res}") res = nested_for_loop(n) - print(f"\n二重forループの走査結果 {res}") \ No newline at end of file + print(f"\n二重 for ループの走査結果 {res}") diff --git a/ja/codes/python/chapter_computational_complexity/recursion.py b/ja/codes/python/chapter_computational_complexity/recursion.py index e5b891536..8c443a264 100644 --- a/ja/codes/python/chapter_computational_complexity/recursion.py +++ b/ja/codes/python/chapter_computational_complexity/recursion.py @@ -12,22 +12,22 @@ def recur(n: int) -> int: return 1 # 再帰:再帰呼び出し res = recur(n - 1) - # 復帰:結果を返す + # 帰りがけ:結果を返す return n + res def for_loop_recur(n: int) -> int: - """反復で再帰をシミュレート""" - # 明示的なスタックを使用してシステムコールスタックをシミュレート + """反復で再帰を模擬する""" + # 明示的なスタックを使ってシステムコールスタックを模擬する stack = [] res = 0 # 再帰:再帰呼び出し for i in range(n, 0, -1): - # 「スタックへのプッシュ」で「再帰」をシミュレート + # 「スタックへのプッシュ」で「再帰」を模擬する stack.append(i) - # 復帰:結果を返す + # 帰りがけ:結果を返す while stack: - # 「スタックからのポップ」で「復帰」をシミュレート + # 「スタックから取り出す操作」で「帰り」をシミュレート res += stack.pop() # res = 1+2+3+...+n return res @@ -47,7 +47,7 @@ def fib(n: int) -> int: # 終了条件 f(1) = 0, f(2) = 1 if n == 1 or n == 2: return n - 1 - # 再帰呼び出し f(n) = f(n-1) + f(n-2) + # f(n) = f(n-1) + f(n-2) を再帰的に呼び出す res = fib(n - 1) + fib(n - 2) # 結果 f(n) を返す return res @@ -60,10 +60,10 @@ if __name__ == "__main__": print(f"\n再帰関数の合計結果 res = {res}") res = for_loop_recur(n) - print(f"\n反復で再帰をシミュレートする合計結果 res = {res}") + print(f"\n反復で再帰をシミュレートした合計結果 res = {res}") res = tail_recur(n, 0) print(f"\n末尾再帰関数の合計結果 res = {res}") res = fib(n) - print(f"\nフィボナッチ数列の第 {n} 項は {res} です") \ No newline at end of file + print(f"\nフィボナッチ数列の第 {n} 項は {res}") diff --git a/ja/codes/python/chapter_computational_complexity/space_complexity.py b/ja/codes/python/chapter_computational_complexity/space_complexity.py index d79aac800..5c0608821 100644 --- a/ja/codes/python/chapter_computational_complexity/space_complexity.py +++ b/ja/codes/python/chapter_computational_complexity/space_complexity.py @@ -13,36 +13,36 @@ from modules import ListNode, TreeNode, print_tree def function() -> int: """関数""" - # 何らかの操作を実行 + # 何らかの処理を行う return 0 def constant(n: int): - """定数複雑度""" - # 定数、変数、オブジェクトは O(1) のスペースを占有 + """定数階""" + # 定数、変数、オブジェクトは O(1) の空間を占める a = 0 nums = [0] * 10000 node = ListNode(0) - # ループ内の変数は O(1) のスペースを占有 + # ループ内の変数は O(1) の空間を占める for _ in range(n): c = 0 - # ループ内の関数は O(1) のスペースを占有 + # ループ内の関数は O(1) の空間を占める for _ in range(n): function() def linear(n: int): - """線形複雑度""" - # 長さ n のリストは O(n) のスペースを占有 + """線形階""" + # 長さ n のリストは O(n) の空間を使用 nums = [0] * n - # 長さ n のハッシュマップは O(n) のスペースを占有 + # 長さ n のハッシュテーブルは O(n) の空間を使用 hmap = dict[int, str]() for i in range(n): hmap[i] = str(i) def linear_recur(n: int): - """線形複雑度(再帰実装)""" + """線形時間(再帰実装)""" print("再帰 n =", n) if n == 1: return @@ -50,22 +50,22 @@ def linear_recur(n: int): def quadratic(n: int): - """平方複雑度""" - # 二次元リストは O(n^2) のスペースを占有 + """二乗階""" + # 二次元リストは O(n^2) の空間を使用 num_matrix = [[0] * n for _ in range(n)] def quadratic_recur(n: int) -> int: - """平方複雑度(再帰実装)""" + """二次時間(再帰実装)""" if n <= 0: return 0 + # 配列 nums の長さは n, n-1, ..., 2, 1 nums = [0] * n - print(f"再帰 n = {n} の中で配列の長さ = {len(nums)}") return quadratic_recur(n - 1) def build_tree(n: int) -> TreeNode | None: - """指数複雑度(完全二分木の構築)""" + """指数時間(完全二分木の構築)""" if n == 0: return None root = TreeNode(0) @@ -77,14 +77,14 @@ def build_tree(n: int) -> TreeNode | None: """Driver Code""" if __name__ == "__main__": n = 5 - # 定数複雑度 + # 定数階 constant(n) - # 線形複雑度 + # 線形階 linear(n) linear_recur(n) - # 平方複雑度 + # 二乗階 quadratic(n) quadratic_recur(n) - # 指数複雑度 + # 指数オーダー root = build_tree(n) print_tree(root) diff --git a/ja/codes/python/chapter_computational_complexity/time_complexity.py b/ja/codes/python/chapter_computational_complexity/time_complexity.py index 19c607da6..1778101ae 100644 --- a/ja/codes/python/chapter_computational_complexity/time_complexity.py +++ b/ja/codes/python/chapter_computational_complexity/time_complexity.py @@ -6,7 +6,7 @@ Author: krahets (krahets@163.com) def constant(n: int) -> int: - """定数複雑度""" + """定数階""" count = 0 size = 100000 for _ in range(size): @@ -15,7 +15,7 @@ def constant(n: int) -> int: def linear(n: int) -> int: - """線形複雑度""" + """線形階""" count = 0 for _ in range(n): count += 1 @@ -23,18 +23,18 @@ def linear(n: int) -> int: def array_traversal(nums: list[int]) -> int: - """線形複雑度(配列の走査)""" + """線形時間(配列を走査)""" count = 0 - # ループ回数は配列の長さに比例する + # ループ回数は配列長に比例する for num in nums: count += 1 return count def quadratic(n: int) -> int: - """二次複雑度""" + """二乗階""" count = 0 - # ループ回数はデータサイズnの二乗に比例する + # ループ回数はデータサイズ n の二乗に比例する for i in range(n): for j in range(n): count += 1 @@ -42,26 +42,26 @@ def quadratic(n: int) -> int: def bubble_sort(nums: list[int]) -> int: - """二次複雑度(バブルソート)""" + """二次時間(バブルソート)""" count = 0 # カウンタ - # 外側のループ: 未ソート範囲は [0, i] + # 外側のループ:未ソート区間は [0, i] for i in range(len(nums) - 1, 0, -1): - # 内側のループ: 未ソート範囲 [0, i] の最大要素を右端にスワップ + # 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for j in range(i): if nums[j] > nums[j + 1]: - # nums[j] と nums[j + 1] をスワップ + # nums[j] と nums[j + 1] を交換 tmp: int = nums[j] nums[j] = nums[j + 1] nums[j + 1] = tmp - count += 3 # 要素のスワップは3つの個別操作を含む + count += 3 # 要素交換には 3 回の単位操作が含まれる return count def exponential(n: int) -> int: - """指数複雑度(ループ実装)""" + """指数時間(ループ実装)""" count = 0 base = 1 - # セルは毎回2つに分裂し、1, 2, 4, 8, ..., 2^(n-1) の数列を形成する + # 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する for _ in range(n): for _ in range(base): count += 1 @@ -71,14 +71,14 @@ def exponential(n: int) -> int: def exp_recur(n: int) -> int: - """指数複雑度(再帰実装)""" + """指数時間(再帰実装)""" if n == 1: return 1 return exp_recur(n - 1) + exp_recur(n - 1) + 1 def logarithmic(n: int) -> int: - """対数複雑度(ループ実装)""" + """対数時間(ループ実装)""" count = 0 while n > 1: n = n / 2 @@ -87,65 +87,67 @@ def logarithmic(n: int) -> int: def log_recur(n: int) -> int: - """対数複雑度(再帰実装)""" + """対数時間(再帰実装)""" if n <= 1: return 0 return log_recur(n / 2) + 1 def linear_log_recur(n: int) -> int: - """線形対数複雑度""" + """線形対数時間""" if n <= 1: return 1 - count: int = linear_log_recur(n // 2) + linear_log_recur(n // 2) + # 二つに分割すると、部分問題の規模は半分になる + count = linear_log_recur(n // 2) + linear_log_recur(n // 2) + # 現在の部分問題には n 個の操作が含まれる for _ in range(n): count += 1 return count def factorial_recur(n: int) -> int: - """階乗複雑度(再帰実装)""" + """階乗時間(再帰実装)""" if n == 0: return 1 count = 0 - # 1つからnに分岐 + # 1個から n 個に分裂 for _ in range(n): count += factorial_recur(n - 1) return count -"""ドライバコード""" +"""Driver Code""" if __name__ == "__main__": - # nを変更して、様々な複雑度での操作回数の変化傾向を体験できる + # n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる n = 8 print("入力データサイズ n =", n) - count: int = constant(n) - print("定数複雑度の操作回数 =", count) + count = constant(n) + print("定数時間の操作回数 =", count) - count: int = linear(n) - print("線形複雑度の操作回数 =", count) - count: int = array_traversal([0] * n) - print("線形複雑度(配列の走査)の操作回数 =", count) + count = linear(n) + print("線形時間の操作回数 =", count) + count = array_traversal([0] * n) + print("線形時間(配列走査)の操作回数 =", count) - count: int = quadratic(n) - print("二次複雑度の操作回数 =", count) + count = quadratic(n) + print("二乗時間の操作回数 =", count) nums = [i for i in range(n, 0, -1)] # [n, n-1, ..., 2, 1] - count: int = bubble_sort(nums) - print("二次複雑度(バブルソート)の操作回数 =", count) + count = bubble_sort(nums) + print("二乗時間(バブルソート)の操作回数 =", count) - count: int = exponential(n) - print("指数複雑度(ループ実装)の操作回数 =", count) - count: int = exp_recur(n) - print("指数複雑度(再帰実装)の操作回数 =", count) + count = exponential(n) + print("指数時間(ループ実装)の操作回数 =", count) + count = exp_recur(n) + print("指数時間(再帰実装)の操作回数 =", count) - count: int = logarithmic(n) - print("対数複雑度(ループ実装)の操作回数 =", count) - count: int = log_recur(n) - print("対数複雑度(再帰実装)の操作回数 =", count) + count = logarithmic(n) + print("対数時間(ループ実装)の操作回数 =", count) + count = log_recur(n) + print("対数時間(再帰実装)の操作回数 =", count) - count: int = linear_log_recur(n) - print("線形対数複雑度(再帰実装)の操作回数 =", count) + count = linear_log_recur(n) + print("線形対数時間(再帰実装)の操作回数 =", count) - count: int = factorial_recur(n) - print("階乗複雑度(再帰実装)の操作回数 =", count) \ No newline at end of file + count = factorial_recur(n) + print("階乗時間(再帰実装)の操作回数 =", count) diff --git a/ja/codes/python/chapter_computational_complexity/worst_best_time_complexity.py b/ja/codes/python/chapter_computational_complexity/worst_best_time_complexity.py index aa4decced..fe135db6f 100644 --- a/ja/codes/python/chapter_computational_complexity/worst_best_time_complexity.py +++ b/ja/codes/python/chapter_computational_complexity/worst_best_time_complexity.py @@ -8,8 +8,8 @@ import random def random_numbers(n: int) -> list[int]: - """要素 1, 2, ..., n を含む配列を生成、順序はシャッフル""" - # 配列 nums = 1, 2, 3, ..., n を生成 + """要素が 1, 2, ..., n で順序がシャッフルされた配列を生成する""" + # 配列 nums =: 1, 2, 3, ..., n を生成する nums = [i for i in range(1, n + 1)] # 配列要素をランダムにシャッフル random.shuffle(nums) @@ -17,10 +17,10 @@ def random_numbers(n: int) -> list[int]: def find_one(nums: list[int]) -> int: - """配列 nums で数値 1 のインデックスを検索""" + """配列 nums 内で数値 1 のインデックスを探す""" for i in range(len(nums)): - # 要素 1 が配列の最初にある場合、最良時間計算量 O(1) を達成 - # 要素 1 が配列の最後にある場合、最悪時間計算量 O(n) を達成 + # 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる + # 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる if nums[i] == 1: return i return -1 @@ -32,5 +32,5 @@ if __name__ == "__main__": n = 100 nums: list[int] = random_numbers(n) index: int = find_one(nums) - print("\nシャッフル後の配列 [ 1, 2, ..., n ] =", nums) - print("数値 1 のインデックス =", index) \ No newline at end of file + print("\n配列 [ 1, 2, ..., n ] をシャッフルすると =", nums) + print("数値 1 のインデックスは", index) diff --git a/ja/codes/python/chapter_divide_and_conquer/binary_search_recur.py b/ja/codes/python/chapter_divide_and_conquer/binary_search_recur.py index 5e2f4dcaf..ff290c460 100644 --- a/ja/codes/python/chapter_divide_and_conquer/binary_search_recur.py +++ b/ja/codes/python/chapter_divide_and_conquer/binary_search_recur.py @@ -7,19 +7,19 @@ Author: krahets (krahets@163.com) def dfs(nums: list[int], target: int, i: int, j: int) -> int: """二分探索:問題 f(i, j)""" - # 区間が空の場合、対象要素がないことを示すため、-1 を返す + # 区間が空なら対象要素は存在しないので -1 を返す if i > j: return -1 # 中点インデックス m を計算 m = (i + j) // 2 if nums[m] < target: - # 再帰部分問題 f(m+1, j) + # 部分問題 f(m+1, j) を再帰的に解く return dfs(nums, target, m + 1, j) elif nums[m] > target: - # 再帰部分問題 f(i, m-1) + # 部分問題 f(i, m-1) を再帰的に解く return dfs(nums, target, i, m - 1) else: - # 対象要素を発見したため、そのインデックスを返す + # 目標要素が見つかったらそのインデックスを返す return m @@ -30,11 +30,11 @@ def binary_search(nums: list[int], target: int) -> int: return dfs(nums, target, 0, n - 1) -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] - # 二分探索(両端閉区間) + # 二分探索(両閉区間) index = binary_search(nums, target) - print("対象要素 6 のインデックス =", index) \ No newline at end of file + print("対象要素 6 のインデックス = ", index) diff --git a/ja/codes/python/chapter_divide_and_conquer/build_tree.py b/ja/codes/python/chapter_divide_and_conquer/build_tree.py index bc16b6eb6..4e017a047 100644 --- a/ja/codes/python/chapter_divide_and_conquer/build_tree.py +++ b/ja/codes/python/chapter_divide_and_conquer/build_tree.py @@ -18,31 +18,31 @@ def dfs( l: int, r: int, ) -> TreeNode | None: - """二分木の構築:分割統治""" - # 部分木の区間が空のとき終了 + """二分木を構築:分割統治""" + # 部分木区間が空なら終了する if r - l < 0: return None - # ルートノードを初期化 + # ルートノードを初期化する root = TreeNode(preorder[i]) - # m をクエリして左部分木と右部分木を分割 + # m を求めて左右部分木を分割する m = inorder_map[preorder[i]] - # 部分問題:左部分木を構築 + # 部分問題:左部分木を構築する root.left = dfs(preorder, inorder_map, i + 1, l, m - 1) - # 部分問題:右部分木を構築 + # 部分問題:右部分木を構築する root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r) - # ルートノードを返す + # 根ノードを返す return root def build_tree(preorder: list[int], inorder: list[int]) -> TreeNode | None: """二分木を構築""" - # ハッシュテーブルを初期化、中順走査の要素からインデックスへのマッピングを保存 + # inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する inorder_map = {val: i for i, val in enumerate(inorder)} root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1) return root -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": preorder = [3, 9, 2, 1, 7] inorder = [9, 3, 1, 2, 7] @@ -50,5 +50,5 @@ if __name__ == "__main__": print(f"中順走査 = {inorder}") root = build_tree(preorder, inorder) - print("構築された二分木は:") - print_tree(root) \ No newline at end of file + print("構築した二分木:") + print_tree(root) diff --git a/ja/codes/python/chapter_divide_and_conquer/hanota.py b/ja/codes/python/chapter_divide_and_conquer/hanota.py index 01a9f50e6..e62343bda 100644 --- a/ja/codes/python/chapter_divide_and_conquer/hanota.py +++ b/ja/codes/python/chapter_divide_and_conquer/hanota.py @@ -6,48 +6,48 @@ Author: krahets (krahets@163.com) def move(src: list[int], tar: list[int]): - """円盤を移動""" - # src の上から円盤を取り出す + """円盤を 1 枚移動""" + # src の上から円盤を1枚取り出す pan = src.pop() # 円盤を tar の上に置く tar.append(pan) def dfs(i: int, src: list[int], buf: list[int], tar: list[int]): - """ハノイの塔問題 f(i) を解く""" - # src に円盤が 1 つだけ残っている場合、それを tar に移動 + """ハノイの塔の問題 f(i) を解く""" + # src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す if i == 1: move(src, tar) return - # 部分問題 f(i-1):tar の助けを借りて src の上の i-1 個の円盤を buf に移動 + # 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す dfs(i - 1, src, tar, buf) - # 部分問題 f(1):残りの 1 個の円盤を src から tar に移動 + # 部分問題 f(1):src に残る 1 枚の円盤を tar に移す move(src, tar) - # 部分問題 f(i-1):src の助けを借りて buf の上の i-1 個の円盤を tar に移動 + # 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す dfs(i - 1, buf, src, tar) def solve_hanota(A: list[int], B: list[int], C: list[int]): - """ハノイの塔問題を解く""" + """ハノイの塔を解く""" n = len(A) - # B の助けを借りて A の上の n 個の円盤を C に移動 + # A の上から n 枚の円盤を B を介して C へ移す dfs(n, A, B, C) -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": - # リストの末尾が柱の上部 + # リスト末尾が柱の頂上 A = [5, 4, 3, 2, 1] B = [] C = [] - print("初期状態:") + print("初期状態:") print(f"A = {A}") print(f"B = {B}") print(f"C = {C}") solve_hanota(A, B, C) - print("円盤移動後:") + print("円盤の移動完了後:") print(f"A = {A}") print(f"B = {B}") - print(f"C = {C}") \ No newline at end of file + print(f"C = {C}") diff --git a/ja/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py b/ja/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py index d75f1916d..80ba81aa8 100644 --- a/ja/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py +++ b/ja/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py @@ -7,31 +7,31 @@ Author: krahets (krahets@163.com) def backtrack(choices: list[int], state: int, n: int, res: list[int]) -> int: """バックトラッキング""" - # n 段目に登ったとき、解の数に 1 を加える + # 第 n 段に到達したら、方法数を 1 増やす if state == n: res[0] += 1 # すべての選択肢を走査 for choice in choices: - # 枝刈り:n 段を超えて登ることを許可しない + # 枝刈り: 第 n 段を超えないようにする if state + choice > n: continue - # 試行:選択を行い、状態を更新 + # 試行: 選択を行い、状態を更新 backtrack(choices, state + choice, n, res) - # 撤回 + # バックトラック def climbing_stairs_backtrack(n: int) -> int: """階段登り:バックトラッキング""" - choices = [1, 2] # 1 段または 2 段登ることを選択可能 - state = 0 # 0 段目から登り始める - res = [0] # res[0] を使用して解の数を記録 + choices = [1, 2] # 1 段または 2 段上ることを選べる + state = 0 # 第 0 段から上り始める + res = [0] # res[0] を使って方法数を記録する backtrack(choices, state, n, res) return res[0] -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_backtrack(n) - print(f"{n} 段登り、合計 {res} 通りの解がある") \ No newline at end of file + print(f"{n} 段の階段を上る方法は全部で {res} 通りです") diff --git a/ja/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py b/ja/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py index 871a4c53e..29e8bb2d7 100644 --- a/ja/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py +++ b/ja/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py @@ -6,24 +6,24 @@ Author: krahets (krahets@163.com) def climbing_stairs_constraint_dp(n: int) -> int: - """制約付き階段登り:動的プログラミング""" + """制約付き階段登り:動的計画法""" if n == 1 or n == 2: return 1 - # dp テーブルを初期化、部分問題の解を格納するために使用 + # 部分問題の解を保存するために dp テーブルを初期化 dp = [[0] * 3 for _ in range(n + 1)] - # 初期状態:最小の部分問題の解を事前設定 + # 初期状態:最小部分問題の解をあらかじめ設定 dp[1][1], dp[1][2] = 1, 0 dp[2][1], dp[2][2] = 0, 1 - # 状態遷移:小さい部分問題から大きい部分問題を段階的に解く + # 状態遷移:小さい部分問題から大きい部分問題へ順に解く for i in range(3, n + 1): dp[i][1] = dp[i - 1][2] dp[i][2] = dp[i - 2][1] + dp[i - 2][2] return dp[n][1] + dp[n][2] -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_constraint_dp(n) - print(f"{n} 段登り、合計 {res} 通りの解がある") \ No newline at end of file + print(f"{n} 段の階段を上る方法は全部で {res} 通りです") diff --git a/ja/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py b/ja/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py index 5c28af1c0..06e6d2ea3 100644 --- a/ja/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py +++ b/ja/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py @@ -6,8 +6,8 @@ Author: krahets (krahets@163.com) def dfs(i: int) -> int: - """探索""" - # 既知の dp[1] と dp[2] は、それらを返す + """検索""" + # dp[1] と dp[2] は既知なので返す if i == 1 or i == 2: return i # dp[i] = dp[i-1] + dp[i-2] @@ -20,9 +20,9 @@ def climbing_stairs_dfs(n: int) -> int: return dfs(n) -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_dfs(n) - print(f"{n} 段登り、合計 {res} 通りの解がある") \ No newline at end of file + print(f"{n} 段の階段を上る方法は全部で {res} 通りです") diff --git a/ja/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py b/ja/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py index 2fdc0b41f..f4cb1eacc 100644 --- a/ja/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py +++ b/ja/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py @@ -6,30 +6,30 @@ Author: krahets (krahets@163.com) def dfs(i: int, mem: list[int]) -> int: - """記憶化探索""" - # 既知の dp[1] と dp[2] は、それらを返す + """メモ化探索""" + # dp[1] と dp[2] は既知なので返す if i == 1 or i == 2: return i - # dp[i] の記録がある場合、それを返す + # dp[i] の記録があれば、それをそのまま返す if mem[i] != -1: return mem[i] # dp[i] = dp[i-1] + dp[i-2] count = dfs(i - 1, mem) + dfs(i - 2, mem) - # dp[i] を記録 + # dp[i] を記録する mem[i] = count return count def climbing_stairs_dfs_mem(n: int) -> int: - """階段登り:記憶化探索""" - # mem[i] は i 段目に登る解の総数を記録、-1 は記録なしを意味する + """階段登り:メモ化探索""" + # mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す mem = [-1] * (n + 1) return dfs(n, mem) -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_dfs_mem(n) - print(f"{n} 段登り、合計 {res} 通りの解がある") \ No newline at end of file + print(f"{n} 段の階段を上る方法は全部で {res} 通りです") diff --git a/ja/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py b/ja/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py index e7f0797f5..39eae97df 100644 --- a/ja/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py +++ b/ja/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py @@ -6,21 +6,21 @@ Author: krahets (krahets@163.com) def climbing_stairs_dp(n: int) -> int: - """階段登り:動的プログラミング""" + """階段登り:動的計画法""" if n == 1 or n == 2: return n - # dp テーブルを初期化、部分問題の解を格納するため使用 + # 部分問題の解を保存するために dp テーブルを初期化 dp = [0] * (n + 1) - # 初期状態:最小の部分問題の解を事前設定 + # 初期状態:最小部分問題の解をあらかじめ設定 dp[1], dp[2] = 1, 2 - # 状態遷移:小さい部分問題から大きい部分問題を段階的に解く + # 状態遷移:小さい部分問題から大きい部分問題へ順に解く for i in range(3, n + 1): dp[i] = dp[i - 1] + dp[i - 2] return dp[n] def climbing_stairs_dp_comp(n: int) -> int: - """階段登り:空間最適化動的プログラミング""" + """階段登り:空間最適化した動的計画法""" if n == 1 or n == 2: return n a, b = 1, 2 @@ -29,12 +29,12 @@ def climbing_stairs_dp_comp(n: int) -> int: return b -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": n = 9 res = climbing_stairs_dp(n) - print(f"{n} 段登り、合計 {res} 通りの解がある") + print(f"{n} 段の階段を上る方法は全部で {res} 通りです") res = climbing_stairs_dp_comp(n) - print(f"{n} 段登り、合計 {res} 通りの解がある") \ No newline at end of file + print(f"{n} 段の階段を上る方法は全部で {res} 通りです") diff --git a/ja/codes/python/chapter_dynamic_programming/coin_change.py b/ja/codes/python/chapter_dynamic_programming/coin_change.py index 9f69c82e7..5401e0a91 100644 --- a/ja/codes/python/chapter_dynamic_programming/coin_change.py +++ b/ja/codes/python/chapter_dynamic_programming/coin_change.py @@ -6,28 +6,28 @@ Author: krahets (krahets@163.com) def coin_change_dp(coins: list[int], amt: int) -> int: - """硬貨交換:動的プログラミング""" + """コイン両替:動的計画法""" n = len(coins) MAX = amt + 1 # dp テーブルを初期化 dp = [[0] * (amt + 1) for _ in range(n + 1)] - # 状態遷移:最初の行と最初の列 + # 状態遷移:先頭行と先頭列 for a in range(1, amt + 1): dp[0][a] = MAX - # 状態遷移:残りの行と列 + # 状態遷移: 残りの行と列 for i in range(1, n + 1): for a in range(1, amt + 1): if coins[i - 1] > a: - # 目標金額を超える場合、硬貨 i を選択しない + # 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a] else: - # 硬貨 i を選択しないのと選択するのとで小さい値 + # 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) return dp[n][amt] if dp[n][amt] != MAX else -1 def coin_change_dp_comp(coins: list[int], amt: int) -> int: - """硬貨交換:空間最適化動的プログラミング""" + """コイン交換:空間最適化後の動的計画法""" n = len(coins) MAX = amt + 1 # dp テーブルを初期化 @@ -35,26 +35,26 @@ def coin_change_dp_comp(coins: list[int], amt: int) -> int: dp[0] = 0 # 状態遷移 for i in range(1, n + 1): - # 順序で走査 + # 順方向に走査する for a in range(1, amt + 1): if coins[i - 1] > a: - # 目標金額を超える場合、硬貨 i を選択しない + # 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a] else: - # 硬貨 i を選択しないのと選択するのとで小さい値 + # 硬貨 i を選ばない場合と選ぶ場合の小さい方 dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) return dp[amt] if dp[amt] != MAX else -1 -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": coins = [1, 2, 5] amt = 4 - # 動的プログラミング + # 動的計画法 res = coin_change_dp(coins, amt) - print(f"目標金額に到達するのに必要な硬貨の最小数 = {res}") + print(f"目標金額を作るのに必要な最小硬貨枚数は {res}") - # 空間最適化動的プログラミング + # 空間最適化後の動的計画法 res = coin_change_dp_comp(coins, amt) - print(f"目標金額に到達するのに必要な硬貨の最小数 = {res}") \ No newline at end of file + print(f"目標金額を作るのに必要な最小硬貨枚数は {res}") diff --git a/ja/codes/python/chapter_dynamic_programming/coin_change_ii.py b/ja/codes/python/chapter_dynamic_programming/coin_change_ii.py index 2f7866e76..523ae5386 100644 --- a/ja/codes/python/chapter_dynamic_programming/coin_change_ii.py +++ b/ja/codes/python/chapter_dynamic_programming/coin_change_ii.py @@ -6,53 +6,53 @@ Author: krahets (krahets@163.com) def coin_change_ii_dp(coins: list[int], amt: int) -> int: - """硬貨交換 II:動的プログラミング""" + """コイン両替 II:動的計画法""" n = len(coins) # dp テーブルを初期化 dp = [[0] * (amt + 1) for _ in range(n + 1)] - # 最初の列を初期化 + # 先頭列を初期化する for i in range(n + 1): dp[i][0] = 1 # 状態遷移 for i in range(1, n + 1): for a in range(1, amt + 1): if coins[i - 1] > a: - # 目標金額を超える場合、硬貨 i を選択しない + # 目標金額を超えるなら硬貨 i は選ばない dp[i][a] = dp[i - 1][a] else: - # 硬貨 i を選択しないのと選択するのとの両方の選択肢の和 + # コイン i を選ばない場合と選ぶ場合の和 dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] return dp[n][amt] def coin_change_ii_dp_comp(coins: list[int], amt: int) -> int: - """硬貨交換 II:空間最適化動的プログラミング""" + """コイン両替 II:空間最適化した動的計画法""" n = len(coins) # dp テーブルを初期化 dp = [0] * (amt + 1) dp[0] = 1 # 状態遷移 for i in range(1, n + 1): - # 順序で走査 + # 順方向に走査する for a in range(1, amt + 1): if coins[i - 1] > a: - # 目標金額を超える場合、硬貨 i を選択しない + # 目標金額を超えるなら硬貨 i は選ばない dp[a] = dp[a] else: - # 硬貨 i を選択しないのと選択するのとの両方の選択肢の和 + # コイン i を選ばない場合と選ぶ場合の和 dp[a] = dp[a] + dp[a - coins[i - 1]] return dp[amt] -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": coins = [1, 2, 5] amt = 5 - # 動的プログラミング + # 動的計画法 res = coin_change_ii_dp(coins, amt) - print(f"目標金額を構成する硬貨の組み合わせ数は {res}") + print(f"目標金額を作る硬貨の組み合わせ数は {res}") - # 空間最適化動的プログラミング + # 空間最適化後の動的計画法 res = coin_change_ii_dp_comp(coins, amt) - print(f"目標金額を構成する硬貨の組み合わせ数は {res}") \ No newline at end of file + print(f"目標金額を作る硬貨の組み合わせ数は {res}") diff --git a/ja/codes/python/chapter_dynamic_programming/edit_distance.py b/ja/codes/python/chapter_dynamic_programming/edit_distance.py index 964d8d06c..9fe84049c 100644 --- a/ja/codes/python/chapter_dynamic_programming/edit_distance.py +++ b/ja/codes/python/chapter_dynamic_programming/edit_distance.py @@ -1,123 +1,123 @@ """ -File: edit_distance.py +File: edit_distancde.py Created Time: 2023-07-04 Author: krahets (krahets@163.com) """ def edit_distance_dfs(s: str, t: str, i: int, j: int) -> int: - """編集距離:ブルートフォース探索""" - # s と t の両方が空の場合、0 を返す + """編集距離:総当たり探索""" + # s と t がともに空なら 0 を返す if i == 0 and j == 0: return 0 - # s が空の場合、t の長さを返す + # s が空なら t の長さを返す if i == 0: return j - # t が空の場合、s の長さを返す + # t が空なら s の長さを返す if j == 0: return i - # 2 つの文字が等しい場合、これら 2 つの文字をスキップ + # 2 つの文字が等しければ、その 2 文字をそのままスキップする if s[i - 1] == t[j - 1]: return edit_distance_dfs(s, t, i - 1, j - 1) - # 最小編集数 = 3 つの操作(挿入、削除、置換)からの最小編集数 + 1 + # 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 insert = edit_distance_dfs(s, t, i, j - 1) delete = edit_distance_dfs(s, t, i - 1, j) replace = edit_distance_dfs(s, t, i - 1, j - 1) - # 最小編集数を返す + # 最小編集回数を返す return min(insert, delete, replace) + 1 def edit_distance_dfs_mem(s: str, t: str, mem: list[list[int]], i: int, j: int) -> int: - """編集距離:記憶化探索""" - # s と t の両方が空の場合、0 を返す + """編集距離:メモ化探索""" + # s と t がともに空なら 0 を返す if i == 0 and j == 0: return 0 - # s が空の場合、t の長さを返す + # s が空なら t の長さを返す if i == 0: return j - # t が空の場合、s の長さを返す + # t が空なら s の長さを返す if j == 0: return i - # 記録がある場合、それを返す + # 記録済みなら、それをそのまま返す if mem[i][j] != -1: return mem[i][j] - # 2 つの文字が等しい場合、これら 2 つの文字をスキップ + # 2 つの文字が等しければ、その 2 文字をそのままスキップする if s[i - 1] == t[j - 1]: return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) - # 最小編集数 = 3 つの操作(挿入、削除、置換)からの最小編集数 + 1 + # 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 insert = edit_distance_dfs_mem(s, t, mem, i, j - 1) delete = edit_distance_dfs_mem(s, t, mem, i - 1, j) replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) - # 最小編集数を記録して返す + # 最小編集回数を記録して返す mem[i][j] = min(insert, delete, replace) + 1 return mem[i][j] def edit_distance_dp(s: str, t: str) -> int: - """編集距離:動的プログラミング""" + """編集距離:動的計画法""" n, m = len(s), len(t) dp = [[0] * (m + 1) for _ in range(n + 1)] - # 状態遷移:最初の行と最初の列 + # 状態遷移:先頭行と先頭列 for i in range(1, n + 1): dp[i][0] = i for j in range(1, m + 1): dp[0][j] = j - # 状態遷移:残りの行と列 + # 状態遷移: 残りの行と列 for i in range(1, n + 1): for j in range(1, m + 1): if s[i - 1] == t[j - 1]: - # 2 つの文字が等しい場合、これら 2 つの文字をスキップ + # 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[i][j] = dp[i - 1][j - 1] else: - # 最小編集数 = 3 つの操作(挿入、削除、置換)からの最小編集数 + 1 + # 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1 return dp[n][m] def edit_distance_dp_comp(s: str, t: str) -> int: - """編集距離:空間最適化動的プログラミング""" + """編集距離:空間最適化した動的計画法""" n, m = len(s), len(t) dp = [0] * (m + 1) - # 状態遷移:最初の行 + # 状態遷移:先頭行 for j in range(1, m + 1): dp[j] = j # 状態遷移:残りの行 for i in range(1, n + 1): - # 状態遷移:最初の列 - leftup = dp[0] # dp[i-1, j-1] を一時的に保存 + # 状態遷移:先頭列 + leftup = dp[0] # dp[i-1, j-1] を一時保存する dp[0] += 1 # 状態遷移:残りの列 for j in range(1, m + 1): temp = dp[j] if s[i - 1] == t[j - 1]: - # 2 つの文字が等しい場合、これら 2 つの文字をスキップ + # 2 つの文字が等しければ、その 2 文字をそのままスキップする dp[j] = leftup else: - # 最小編集数 = 3 つの操作(挿入、削除、置換)からの最小編集数 + 1 + # 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 dp[j] = min(dp[j - 1], dp[j], leftup) + 1 - leftup = temp # 次の dp[i-1, j-1] のために更新 + leftup = temp # 次の反復の dp[i-1, j-1] に更新する return dp[m] -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": s = "bag" t = "pack" n, m = len(s), len(t) - # ブルートフォース探索 + # 全探索 res = edit_distance_dfs(s, t, n, m) - print(f"{s} を {t} に変更するために必要な最小編集数は {res}") + print(f"{s} を {t} に変更するには最小で {res} 回の編集が必要です") - # 記憶化探索 + # メモ化探索 mem = [[-1] * (m + 1) for _ in range(n + 1)] res = edit_distance_dfs_mem(s, t, mem, n, m) - print(f"{s} を {t} に変更するために必要な最小編集数は {res}") + print(f"{s} を {t} に変更するには最小で {res} 回の編集が必要です") - # 動的プログラミング + # 動的計画法 res = edit_distance_dp(s, t) - print(f"{s} を {t} に変更するために必要な最小編集数は {res}") + print(f"{s} を {t} に変更するには最小で {res} 回の編集が必要です") - # 空間最適化動的プログラミング + # 空間最適化後の動的計画法 res = edit_distance_dp_comp(s, t) - print(f"{s} を {t} に変更するために必要な最小編集数は {res}") \ No newline at end of file + print(f"{s} を {t} に変更するには最小で {res} 回の編集が必要です") diff --git a/ja/codes/python/chapter_dynamic_programming/knapsack.py b/ja/codes/python/chapter_dynamic_programming/knapsack.py index 82ab6f3ef..c38e55526 100644 --- a/ja/codes/python/chapter_dynamic_programming/knapsack.py +++ b/ja/codes/python/chapter_dynamic_programming/knapsack.py @@ -6,43 +6,43 @@ Author: krahets (krahets@163.com) def knapsack_dfs(wgt: list[int], val: list[int], i: int, c: int) -> int: - """0-1 ナップサック:ブルートフォース探索""" - # すべてのアイテムが選択されたかナップサックに残り容量がない場合、値 0 を返す + """0-1 ナップサック:総当たり探索""" + # すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if i == 0 or c == 0: return 0 - # ナップサック容量を超える場合、ナップサックに入れないことしか選択できない + # ナップサック容量を超える場合は、入れない選択しかできない if wgt[i - 1] > c: return knapsack_dfs(wgt, val, i - 1, c) - # アイテム i を入れないのと入れるのとの最大値を計算 + # 品物 i を入れない場合と入れる場合の最大価値を計算する no = knapsack_dfs(wgt, val, i - 1, c) yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] - # 2 つの選択肢のうち大きい値を返す + # 2つの案のうち価値が大きいほうを返す return max(no, yes) def knapsack_dfs_mem( wgt: list[int], val: list[int], mem: list[list[int]], i: int, c: int ) -> int: - """0-1 ナップサック:記憶化探索""" - # すべてのアイテムが選択されたかナップサックに残り容量がない場合、値 0 を返す + """0-1 ナップサック:メモ化探索""" + # すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す if i == 0 or c == 0: return 0 - # 記録がある場合、それを返す + # 既に記録があればそのまま返す if mem[i][c] != -1: return mem[i][c] - # ナップサック容量を超える場合、ナップサックに入れないことしか選択できない + # ナップサック容量を超える場合は、入れない選択しかできない if wgt[i - 1] > c: return knapsack_dfs_mem(wgt, val, mem, i - 1, c) - # アイテム i を入れないのと入れるのとの最大値を計算 + # 品物 i を入れない場合と入れる場合の最大価値を計算する no = knapsack_dfs_mem(wgt, val, mem, i - 1, c) yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1] - # 2 つの選択肢のうち大きい値を記録して返す + # 2 つの案のうち価値が大きい方を記録して返す mem[i][c] = max(no, yes) return mem[i][c] def knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: - """0-1 ナップサック:動的プログラミング""" + """0-1 ナップサック:動的計画法""" n = len(wgt) # dp テーブルを初期化 dp = [[0] * (cap + 1) for _ in range(n + 1)] @@ -50,52 +50,52 @@ def knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: for i in range(1, n + 1): for c in range(1, cap + 1): if wgt[i - 1] > c: - # ナップサック容量を超える場合、アイテム i を選択しない + # ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c] else: - # アイテム i を選択しないのと選択するのとで大きい値 + # 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) return dp[n][cap] def knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int: - """0-1 ナップサック:空間最適化動的プログラミング""" + """0-1 ナップサック:空間最適化後の動的計画法""" n = len(wgt) # dp テーブルを初期化 dp = [0] * (cap + 1) # 状態遷移 for i in range(1, n + 1): - # 逆順で走査 + # 逆順に走査する for c in range(cap, 0, -1): if wgt[i - 1] > c: - # ナップサック容量を超える場合、アイテム i を選択しない + # ナップサック容量を超えるなら品物 i は選ばない dp[c] = dp[c] else: - # アイテム i を選択しないのと選択するのとで大きい値 + # 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) return dp[cap] -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = len(wgt) - # ブルートフォース探索 + # 全探索 res = knapsack_dfs(wgt, val, n, cap) - print(f"ナップサック容量を超えないアイテムの最大値は {res}") + print(f"ナップサック容量を超えない最大価値は {res}") - # 記憶化探索 + # メモ化探索 mem = [[-1] * (cap + 1) for _ in range(n + 1)] res = knapsack_dfs_mem(wgt, val, mem, n, cap) - print(f"ナップサック容量を超えないアイテムの最大値は {res}") + print(f"ナップサック容量を超えない最大価値は {res}") - # 動的プログラミング + # 動的計画法 res = knapsack_dp(wgt, val, cap) - print(f"ナップサック容量を超えないアイテムの最大値は {res}") + print(f"ナップサック容量を超えない最大価値は {res}") - # 空間最適化動的プログラミング + # 空間最適化後の動的計画法 res = knapsack_dp_comp(wgt, val, cap) - print(f"ナップサック容量を超えないアイテムの最大値は {res}") \ No newline at end of file + print(f"ナップサック容量を超えない最大価値は {res}") diff --git a/ja/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py b/ja/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py index 2fdf731cd..797181c65 100644 --- a/ja/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py +++ b/ja/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py @@ -6,22 +6,22 @@ Author: krahets (krahets@163.com) def min_cost_climbing_stairs_dp(cost: list[int]) -> int: - """最小コスト階段登り:動的プログラミング""" + """階段登りの最小コスト:動的計画法""" n = len(cost) - 1 if n == 1 or n == 2: return cost[n] - # dp テーブルを初期化、部分問題の解を格納するために使用 + # 部分問題の解を保存するために dp テーブルを初期化 dp = [0] * (n + 1) - # 初期状態:最小の部分問題の解を事前設定 + # 初期状態:最小部分問題の解をあらかじめ設定 dp[1], dp[2] = cost[1], cost[2] - # 状態遷移:小さい部分問題から大きい部分問題を段階的に解く + # 状態遷移:小さい部分問題から大きい部分問題へ順に解く for i in range(3, n + 1): dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] return dp[n] def min_cost_climbing_stairs_dp_comp(cost: list[int]) -> int: - """最小コスト階段登り:空間最適化動的プログラミング""" + """階段昇りの最小コスト:空間最適化後の動的計画法""" n = len(cost) - 1 if n == 1 or n == 2: return cost[n] @@ -31,13 +31,13 @@ def min_cost_climbing_stairs_dp_comp(cost: list[int]) -> int: return b -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] - print(f"階段コストリストの入力:{cost}") + print(f"入力された階段コストのリストは {cost}") res = min_cost_climbing_stairs_dp(cost) - print(f"階段を登る最小コスト {res}") + print(f"階段を上り切る最小コストは {res}") res = min_cost_climbing_stairs_dp_comp(cost) - print(f"階段を登る最小コスト {res}") \ No newline at end of file + print(f"階段を上り切る最小コストは {res}") diff --git a/ja/codes/python/chapter_dynamic_programming/min_path_sum.py b/ja/codes/python/chapter_dynamic_programming/min_path_sum.py index 3e74327d9..2de5ad174 100644 --- a/ja/codes/python/chapter_dynamic_programming/min_path_sum.py +++ b/ja/codes/python/chapter_dynamic_programming/min_path_sum.py @@ -8,54 +8,54 @@ from math import inf def min_path_sum_dfs(grid: list[list[int]], i: int, j: int) -> int: - """最小パス和:ブルートフォース探索""" - # 左上のセルの場合、探索を終了 + """最小経路和:全探索""" + # 左上のセルなら探索を終了する if i == 0 and j == 0: return grid[0][0] - # 行または列のインデックスが範囲外の場合、+∞ コストを返す + # 行または列のインデックスが範囲外なら、コスト +∞ を返す if i < 0 or j < 0: return inf - # 左上から (i-1, j) と (i, j-1) への最小パスコストを計算 + # 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する up = min_path_sum_dfs(grid, i - 1, j) left = min_path_sum_dfs(grid, i, j - 1) - # 左上から (i, j) への最小パスコストを返す + # 左上隅から (i, j) までの最小経路コストを返す return min(left, up) + grid[i][j] def min_path_sum_dfs_mem( grid: list[list[int]], mem: list[list[int]], i: int, j: int ) -> int: - """最小パス和:記憶化探索""" - # 左上のセルの場合、探索を終了 + """最小経路和:メモ化探索""" + # 左上のセルなら探索を終了する if i == 0 and j == 0: return grid[0][0] - # 行または列のインデックスが範囲外の場合、+∞ コストを返す + # 行または列のインデックスが範囲外なら、コスト +∞ を返す if i < 0 or j < 0: return inf - # 記録がある場合、それを返す + # 既に記録があればそのまま返す if mem[i][j] != -1: return mem[i][j] - # 左と上のセルからの最小パスコスト + # 左と上のセルからの最小経路コスト up = min_path_sum_dfs_mem(grid, mem, i - 1, j) left = min_path_sum_dfs_mem(grid, mem, i, j - 1) - # 左上から (i, j) への最小パスコストを記録して返す + # 左上から (i, j) までの最小経路コストを記録して返す mem[i][j] = min(left, up) + grid[i][j] return mem[i][j] def min_path_sum_dp(grid: list[list[int]]) -> int: - """最小パス和:動的プログラミング""" + """最小経路和:動的計画法""" n, m = len(grid), len(grid[0]) # dp テーブルを初期化 dp = [[0] * m for _ in range(n)] dp[0][0] = grid[0][0] - # 状態遷移:最初の行 + # 状態遷移:先頭行 for j in range(1, m): dp[0][j] = dp[0][j - 1] + grid[0][j] - # 状態遷移:最初の列 + # 状態遷移:先頭列 for i in range(1, n): dp[i][0] = dp[i - 1][0] + grid[i][0] - # 状態遷移:残りの行と列 + # 状態遷移: 残りの行と列 for i in range(1, n): for j in range(1, m): dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] @@ -63,17 +63,17 @@ def min_path_sum_dp(grid: list[list[int]]) -> int: def min_path_sum_dp_comp(grid: list[list[int]]) -> int: - """最小パス和:空間最適化動的プログラミング""" + """最小経路和:空間最適化後の動的計画法""" n, m = len(grid), len(grid[0]) # dp テーブルを初期化 dp = [0] * m - # 状態遷移:最初の行 + # 状態遷移:先頭行 dp[0] = grid[0][0] for j in range(1, m): dp[j] = dp[j - 1] + grid[0][j] # 状態遷移:残りの行 for i in range(1, n): - # 状態遷移:最初の列 + # 状態遷移:先頭列 dp[0] = dp[0] + grid[i][0] # 状態遷移:残りの列 for j in range(1, m): @@ -81,24 +81,24 @@ def min_path_sum_dp_comp(grid: list[list[int]]) -> int: return dp[m - 1] -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] n, m = len(grid), len(grid[0]) - # ブルートフォース探索 + # 全探索 res = min_path_sum_dfs(grid, n - 1, m - 1) - print(f"左上から右下角への最小パス和は {res}") + print(f"左上から右下までの最小経路和は {res}") - # 記憶化探索 + # メモ化探索 mem = [[-1] * m for _ in range(n)] res = min_path_sum_dfs_mem(grid, mem, n - 1, m - 1) - print(f"左上から右下角への最小パス和は {res}") + print(f"左上から右下までの最小経路和は {res}") - # 動的プログラミング + # 動的計画法 res = min_path_sum_dp(grid) - print(f"左上から右下角への最小パス和は {res}") + print(f"左上から右下までの最小経路和は {res}") - # 空間最適化動的プログラミング + # 空間最適化後の動的計画法 res = min_path_sum_dp_comp(grid) - print(f"左上から右下角への最小パス和は {res}") \ No newline at end of file + print(f"左上から右下までの最小経路和は {res}") diff --git a/ja/codes/python/chapter_dynamic_programming/unbounded_knapsack.py b/ja/codes/python/chapter_dynamic_programming/unbounded_knapsack.py index 1751bd1c4..060d552d6 100644 --- a/ja/codes/python/chapter_dynamic_programming/unbounded_knapsack.py +++ b/ja/codes/python/chapter_dynamic_programming/unbounded_knapsack.py @@ -6,7 +6,7 @@ Author: krahets (krahets@163.com) def unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: - """完全ナップサック:動的プログラミング""" + """完全ナップサック問題:動的計画法""" n = len(wgt) # dp テーブルを初期化 dp = [[0] * (cap + 1) for _ in range(n + 1)] @@ -14,42 +14,42 @@ def unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: for i in range(1, n + 1): for c in range(1, cap + 1): if wgt[i - 1] > c: - # ナップサック容量を超える場合、アイテム i を選択しない + # ナップサック容量を超えるなら品物 i は選ばない dp[i][c] = dp[i - 1][c] else: - # アイテム i を選択しないのと選択するのとで大きい値 + # 品物 i を選ばない場合と選ぶ場合の大きい方 dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) return dp[n][cap] def unbounded_knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int: - """完全ナップサック:空間最適化動的プログラミング""" + """完全ナップサック問題:空間最適化後の動的計画法""" n = len(wgt) # dp テーブルを初期化 dp = [0] * (cap + 1) # 状態遷移 for i in range(1, n + 1): - # 順序で走査 + # 順方向に走査する for c in range(1, cap + 1): if wgt[i - 1] > c: - # ナップサック容量を超える場合、アイテム i を選択しない + # ナップサック容量を超えるなら品物 i は選ばない dp[c] = dp[c] else: - # アイテム i を選択しないのと選択するのとで大きい値 + # 品物 i を選ばない場合と選ぶ場合の大きい方 dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) return dp[cap] -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": wgt = [1, 2, 3] val = [5, 11, 15] cap = 4 - # 動的プログラミング + # 動的計画法 res = unbounded_knapsack_dp(wgt, val, cap) - print(f"ナップサック容量を超えないアイテムの最大値は {res}") + print(f"ナップサック容量を超えない最大価値は {res}") - # 空間最適化動的プログラミング + # 空間最適化後の動的計画法 res = unbounded_knapsack_dp_comp(wgt, val, cap) - print(f"ナップサック容量を超えないアイテムの最大値は {res}") \ No newline at end of file + print(f"ナップサック容量を超えない最大価値は {res}") diff --git a/ja/codes/python/chapter_graph/graph_adjacency_list.py b/ja/codes/python/chapter_graph/graph_adjacency_list.py index 67ea4a03c..0c053acac 100644 --- a/ja/codes/python/chapter_graph/graph_adjacency_list.py +++ b/ja/codes/python/chapter_graph/graph_adjacency_list.py @@ -16,7 +16,7 @@ class GraphAdjList: def __init__(self, edges: list[list[Vertex]]): """コンストラクタ""" - # 隣接リスト、キー: 頂点、値: その頂点の隣接する全頂点 + # 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 self.adj_list = dict[Vertex, list[Vertex]]() # すべての頂点と辺を追加 for edge in edges: @@ -48,16 +48,16 @@ class GraphAdjList: """頂点を追加""" if vet in self.adj_list: return - # 隣接リストに新しい連結リストを追加 + # 隣接リストに新しいリストを追加 self.adj_list[vet] = [] def remove_vertex(self, vet: Vertex): """頂点を削除""" if vet not in self.adj_list: raise ValueError() - # 隣接リストから頂点vetに対応する連結リストを削除 + # 隣接リストから頂点 vet に対応するリストを削除 self.adj_list.pop(vet) - # 他の頂点の連結リストを走査し、vetを含むすべての辺を削除 + # 他の頂点のリストを走査し、vet を含むすべての辺を削除 for vertex in self.adj_list: if vet in self.adj_list[vertex]: self.adj_list[vertex].remove(vet) @@ -70,7 +70,7 @@ class GraphAdjList: print(f"{vertex.val}: {tmp},") -"""ドライバコード""" +"""Driver Code""" if __name__ == "__main__": # 無向グラフを初期化 v = vals_to_vets([1, 3, 2, 5, 4]) @@ -86,26 +86,26 @@ if __name__ == "__main__": print("\n初期化後、グラフは") graph.print() - # 辺を追加 - # 頂点1、2 つまり v[0], v[2] + # 辺を追加する + # 頂点 1, 2 は `v[0]`, `v[2]` graph.add_edge(v[0], v[2]) - print("\n辺1-2を追加後、グラフは") + print("\n辺 1-2 を追加した後、グラフは") graph.print() - # 辺を削除 - # 頂点1、3 つまり v[0], v[1] + # 辺を削除する + # 頂点 1, 3 はそれぞれ v[0], v[1] graph.remove_edge(v[0], v[1]) - print("\n辺1-3を削除後、グラフは") + print("\n辺 1-3 を削除した後、グラフは") graph.print() # 頂点を追加 v5 = Vertex(6) graph.add_vertex(v5) - print("\n頂点6を追加後、グラフは") + print("\n頂点 6 を追加した後、グラフは") graph.print() - # 頂点を削除 - # 頂点3 つまり v[1] + # 頂点を削除する + # 頂点 3 は v[1] graph.remove_vertex(v[1]) - print("\n頂点3を削除後、グラフは") - graph.print() \ No newline at end of file + print("\n頂点 3 を削除した後、グラフは") + graph.print() diff --git a/ja/codes/python/chapter_graph/graph_adjacency_matrix.py b/ja/codes/python/chapter_graph/graph_adjacency_matrix.py index 2646320b7..3d278508a 100644 --- a/ja/codes/python/chapter_graph/graph_adjacency_matrix.py +++ b/ja/codes/python/chapter_graph/graph_adjacency_matrix.py @@ -16,15 +16,15 @@ class GraphAdjMat: def __init__(self, vertices: list[int], edges: list[list[int]]): """コンストラクタ""" - # 頂点リスト、要素は「頂点値」を表し、インデックスは「頂点インデックス」を表す + # 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す self.vertices: list[int] = [] - # 隣接行列、行と列のインデックスは「頂点インデックス」に対応 + # 隣接行列。行・列のインデックスは「頂点インデックス」に対応 self.adj_mat: list[list[int]] = [] # 頂点を追加 for val in vertices: self.add_vertex(val) # 辺を追加 - # edges要素は頂点インデックスを表す + # 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する for e in edges: self.add_edge(e[0], e[1]) @@ -35,12 +35,12 @@ class GraphAdjMat: def add_vertex(self, val: int): """頂点を追加""" n = self.size() - # 頂点リストに新しい頂点値を追加 + # 頂点リストに新しい頂点の値を追加 self.vertices.append(val) - # 隣接行列に行を追加 + # 隣接行列に 1 行追加 new_row = [0] * n self.adj_mat.append(new_row) - # 隣接行列に列を追加 + # 隣接行列に 1 列追加 for row in self.adj_mat: row.append(0) @@ -48,28 +48,28 @@ class GraphAdjMat: """頂点を削除""" if index >= self.size(): raise IndexError() - # 頂点リストから`index`の頂点を削除 + # 頂点リストから index の頂点を削除する self.vertices.pop(index) - # 隣接行列から`index`の行を削除 + # 隣接行列で index 行を削除する self.adj_mat.pop(index) - # 隣接行列から`index`の列を削除 + # 隣接行列で index 列を削除する for row in self.adj_mat: row.pop(index) def add_edge(self, i: int, j: int): """辺を追加""" - # パラメータi、jは頂点要素のインデックスに対応 - # インデックスの範囲外と等価性を処理 + # パラメータ i, j は vertices の要素インデックスに対応する + # 範囲外と同値の場合の処理 if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: raise IndexError() - # 無向グラフでは、隣接行列は主対角線について対称、すなわち (i, j) == (j, i) を満たす + # 無向グラフでは、隣接行列は主対角線に関して対称、すなわち (i, j) == (j, i) を満たす self.adj_mat[i][j] = 1 self.adj_mat[j][i] = 1 def remove_edge(self, i: int, j: int): """辺を削除""" - # パラメータi、jは頂点要素のインデックスに対応 - # インデックスの範囲外と等価性を処理 + # パラメータ i, j は vertices の要素インデックスに対応する + # 範囲外と同値の場合の処理 if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: raise IndexError() self.adj_mat[i][j] = 0 @@ -82,35 +82,35 @@ class GraphAdjMat: print_matrix(self.adj_mat) -"""ドライバコード""" +"""Driver Code""" if __name__ == "__main__": - # 無向グラフを初期化 - # edges要素は頂点インデックスを表す + # 無向グラフを初期化する + # 注意: edges の要素は頂点インデックスであり、vertices の要素インデックスに対応する vertices = [1, 3, 2, 5, 4] edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] graph = GraphAdjMat(vertices, edges) print("\n初期化後、グラフは") graph.print() - # 辺を追加 - # 頂点1、2のインデックスはそれぞれ0、2 + # 辺を追加する + # 頂点 1, 2 のインデックスはそれぞれ 0, 2 graph.add_edge(0, 2) - print("\n辺1-2を追加後、グラフは") + print("\n辺 1-2 を追加した後、グラフは") graph.print() - # 辺を削除 - # 頂点1、3のインデックスはそれぞれ0、1 + # 辺を削除する + # 頂点 1, 3 のインデックスはそれぞれ 0, 1 graph.remove_edge(0, 1) - print("\n辺1-3を削除後、グラフは") + print("\n辺 1-3 を削除した後、グラフは") graph.print() # 頂点を追加 graph.add_vertex(6) - print("\n頂点6を追加後、グラフは") + print("\n頂点 6 を追加した後、グラフは") graph.print() - # 頂点を削除 - # 頂点3のインデックスは1 + # 頂点を削除する + # 頂点 3 のインデックスは 1 graph.remove_vertex(1) - print("\n頂点3を削除後、グラフは") - graph.print() \ No newline at end of file + print("\n頂点 3 を削除した後、グラフは") + graph.print() diff --git a/ja/codes/python/chapter_graph/graph_bfs.py b/ja/codes/python/chapter_graph/graph_bfs.py index 4462c5290..d18adae98 100644 --- a/ja/codes/python/chapter_graph/graph_bfs.py +++ b/ja/codes/python/chapter_graph/graph_bfs.py @@ -14,29 +14,29 @@ from graph_adjacency_list import GraphAdjList def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: - """幅優先走査""" - # 隣接リストを使用してグラフを表現し、指定された頂点のすべての隣接頂点を取得 - # 頂点走査シーケンス + """幅優先探索""" + # 指定した頂点の隣接頂点をすべて取得できるよう、隣接リストでグラフを表現する + # 頂点の走査順序 res = [] - # ハッシュセット、訪問済み頂点を記録するために使用 + # 訪問済み頂点を記録するためのハッシュ集合 visited = set[Vertex]([start_vet]) - # BFSを実装するために使用されるキュー + # BFS の実装にキューを用いる que = deque[Vertex]([start_vet]) - # 頂点vetから開始し、すべての頂点が訪問されるまでループ + # 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す while len(que) > 0: - vet = que.popleft() # キューの先頭の頂点をデキュー - res.append(vet) # 訪問済み頂点を記録 - # その頂点のすべての隣接頂点を走査 + vet = que.popleft() # 先頭の頂点をデキュー + res.append(vet) # 訪問した頂点を記録 + # この頂点のすべての隣接頂点を走査 for adj_vet in graph.adj_list[vet]: if adj_vet in visited: - continue # 既に訪問済みの頂点をスキップ - que.append(adj_vet) # 未訪問の頂点のみをエンキュー - visited.add(adj_vet) # 頂点を訪問済みとしてマーク - # 頂点走査シーケンスを返す + continue # 訪問済みの頂点をスキップ + que.append(adj_vet) # 未訪問の頂点のみをキューに追加 + visited.add(adj_vet) # この頂点を訪問済みにする + # 頂点の走査順を返す return res -"""ドライバコード""" +"""Driver Code""" if __name__ == "__main__": # 無向グラフを初期化 v = vals_to_vets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) @@ -58,7 +58,7 @@ if __name__ == "__main__": print("\n初期化後、グラフは") graph.print() - # 幅優先走査 + # 幅優先探索 res = graph_bfs(graph, v[0]) - print("\n幅優先走査(BFS)の頂点シーケンスは") - print(vets_to_vals(res)) \ No newline at end of file + print("\n幅優先探索(BFS)の頂点順序は") + print(vets_to_vals(res)) diff --git a/ja/codes/python/chapter_graph/graph_dfs.py b/ja/codes/python/chapter_graph/graph_dfs.py index c6b150b7e..60bd2529d 100644 --- a/ja/codes/python/chapter_graph/graph_dfs.py +++ b/ja/codes/python/chapter_graph/graph_dfs.py @@ -13,29 +13,29 @@ from graph_adjacency_list import GraphAdjList def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex): - """深さ優先走査のヘルパー関数""" - res.append(vet) # 訪問済み頂点を記録 - visited.add(vet) # 頂点を訪問済みとしてマーク - # その頂点のすべての隣接頂点を走査 + """深さ優先走査の補助関数""" + res.append(vet) # 訪問した頂点を記録 + visited.add(vet) # この頂点を訪問済みにする + # この頂点のすべての隣接頂点を走査 for adjVet in graph.adj_list[vet]: if adjVet in visited: - continue # 既に訪問済みの頂点をスキップ + continue # 訪問済みの頂点をスキップ # 隣接頂点を再帰的に訪問 dfs(graph, visited, res, adjVet) def graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: - """深さ優先走査""" - # 隣接リストを使用してグラフを表現し、指定された頂点のすべての隣接頂点を取得 - # 頂点走査シーケンス + """深さ優先探索""" + # 指定した頂点の隣接頂点をすべて取得できるよう、隣接リストでグラフを表現する + # 頂点の走査順序 res = [] - # ハッシュセット、訪問済み頂点を記録するために使用 + # 訪問済み頂点を記録するためのハッシュ集合 visited = set[Vertex]() dfs(graph, visited, res, start_vet) return res -"""ドライバコード""" +"""Driver Code""" if __name__ == "__main__": # 無向グラフを初期化 v = vals_to_vets([0, 1, 2, 3, 4, 5, 6]) @@ -51,7 +51,7 @@ if __name__ == "__main__": print("\n初期化後、グラフは") graph.print() - # 深さ優先走査 + # 深さ優先探索 res = graph_dfs(graph, v[0]) - print("\n深さ優先走査(DFS)の頂点シーケンスは") - print(vets_to_vals(res)) \ No newline at end of file + print("\n深さ優先探索(DFS)の頂点順序は") + print(vets_to_vals(res)) diff --git a/ja/codes/python/chapter_greedy/coin_change_greedy.py b/ja/codes/python/chapter_greedy/coin_change_greedy.py index ca8592204..8553144a0 100644 --- a/ja/codes/python/chapter_greedy/coin_change_greedy.py +++ b/ja/codes/python/chapter_greedy/coin_change_greedy.py @@ -6,43 +6,43 @@ Author: krahets (krahets@163.com) def coin_change_greedy(coins: list[int], amt: int) -> int: - """硬貨交換:貪欲法""" - # coins リストがソートされていると仮定 + """コイン交換:貪欲法""" + # coins リストはソート済みと仮定する i = len(coins) - 1 count = 0 - # 残り金額がなくなるまで貪欲選択をループ + # 残額がなくなるまで貪欲選択を繰り返す while amt > 0: - # 残り金額に最も近く、それより小さい硬貨を見つける + # 残額以下で最も近い硬貨を見つける while i > 0 and coins[i] > amt: i -= 1 - # coins[i] を選択 + # coins[i] を選択する amt -= coins[i] count += 1 - # 実行可能な解が見つからない場合、-1 を返す + # 実行可能な解が見つからなければ -1 を返す return count if amt == 0 else -1 -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": - # 貪欲法:大域最適解の発見を保証できる + # 貪欲法:大域最適解を保証できる coins = [1, 5, 10, 20, 50, 100] amt = 186 res = coin_change_greedy(coins, amt) print(f"\ncoins = {coins}, amt = {amt}") - print(f"{amt} を構成するのに必要な硬貨の最小数は {res}") + print(f"{amt} を作るのに必要な最小の硬貨枚数は {res}") - # 貪欲法:大域最適解の発見を保証できない + # 貪欲法:大域最適解を保証できない coins = [1, 20, 50] amt = 60 res = coin_change_greedy(coins, amt) print(f"\ncoins = {coins}, amt = {amt}") - print(f"{amt} を構成するのに必要な硬貨の最小数は {res}") - print(f"実際には必要な最小数は 3、つまり 20 + 20 + 20") + print(f"{amt} を作るのに必要な最小の硬貨枚数は {res}") + print(f"実際に必要な最小枚数は 3 ,つまり 20 + 20 + 20") - # 貪欲法:大域最適解の発見を保証できない + # 貪欲法:大域最適解を保証できない coins = [1, 49, 50] amt = 98 res = coin_change_greedy(coins, amt) print(f"\ncoins = {coins}, amt = {amt}") - print(f"{amt} を構成するのに必要な硬貨の最小数は {res}") - print(f"実際には必要な最小数は 2、つまり 49 + 49") \ No newline at end of file + print(f"{amt} を作るのに必要な最小の硬貨枚数は {res}") + print(f"実際に必要な最小枚数は 2 ,つまり 49 + 49") diff --git a/ja/codes/python/chapter_greedy/fractional_knapsack.py b/ja/codes/python/chapter_greedy/fractional_knapsack.py index 950c68422..b01281d60 100644 --- a/ja/codes/python/chapter_greedy/fractional_knapsack.py +++ b/ja/codes/python/chapter_greedy/fractional_knapsack.py @@ -6,41 +6,41 @@ Author: krahets (krahets@163.com) class Item: - """アイテム""" + """品物""" def __init__(self, w: int, v: int): - self.w = w # アイテムの重量 - self.v = v # アイテムの価値 + self.w = w # 品物の重さ + self.v = v # 品物の価値 def fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int: """分数ナップサック:貪欲法""" - # アイテムリストを作成、2 つの属性を含む:重量、価値 + # 重さと価値の 2 属性を持つ品物リストを作成 items = [Item(w, v) for w, v in zip(wgt, val)] - # 単位価値 item.v / item.w で高い順にソート + # 単位価値 item.v / item.w の高い順にソートする items.sort(key=lambda item: item.v / item.w, reverse=True) - # 貪欲選択をループ + # 貪欲選択を繰り返す res = 0 for item in items: if item.w <= cap: - # 残り容量が十分な場合、アイテム全体をナップサックに入れる + # 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる res += item.v cap -= item.w else: - # 残り容量が不十分な場合、アイテムの一部をナップサックに入れる + # 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる res += (item.v / item.w) * cap - # 残り容量がなくなったため、ループを中断 + # 残り容量がないため、ループを抜ける break return res -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": wgt = [10, 20, 30, 40, 50] val = [50, 120, 150, 210, 240] cap = 50 n = len(wgt) - # 貪欲アルゴリズム + # 貪欲法 res = fractional_knapsack(wgt, val, cap) - print(f"ナップサック容量を超えないアイテムの最大値は {res}") \ No newline at end of file + print(f"ナップサック容量を超えない最大価値は {res}") diff --git a/ja/codes/python/chapter_greedy/max_capacity.py b/ja/codes/python/chapter_greedy/max_capacity.py index f3b37e106..78eb2eaec 100644 --- a/ja/codes/python/chapter_greedy/max_capacity.py +++ b/ja/codes/python/chapter_greedy/max_capacity.py @@ -7,16 +7,16 @@ Author: krahets (krahets@163.com) def max_capacity(ht: list[int]) -> int: """最大容量:貪欲法""" - # i、j を初期化、配列の両端で分割させる + # i, j を初期化し、それぞれ配列の両端に置く i, j = 0, len(ht) - 1 - # 初期最大容量は 0 + # 初期の最大容量は 0 res = 0 - # 2 つの板が出会うまで貪欲選択をループ + # 2 枚の板が出会うまで貪欲選択を繰り返す while i < j: - # 最大容量を更新 + # 最大容量を更新する cap = min(ht[i], ht[j]) * (j - i) res = max(res, cap) - # 短い板を内側に移動 + # 短い方を内側へ動かす if ht[i] < ht[j]: i += 1 else: @@ -24,10 +24,10 @@ def max_capacity(ht: list[int]) -> int: return res -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": ht = [3, 8, 5, 2, 7, 7, 3, 4] - # 貪欲アルゴリズム + # 貪欲法 res = max_capacity(ht) - print(f"最大容量は {res}") \ No newline at end of file + print(f"最大容量は {res}") diff --git a/ja/codes/python/chapter_greedy/max_product_cutting.py b/ja/codes/python/chapter_greedy/max_product_cutting.py index 192e53a5e..98dd931e1 100644 --- a/ja/codes/python/chapter_greedy/max_product_cutting.py +++ b/ja/codes/python/chapter_greedy/max_product_cutting.py @@ -8,26 +8,26 @@ import math def max_product_cutting(n: int) -> int: - """切断の最大積:貪欲法""" - # n <= 3 の場合、1 を切り出す必要がある + """最大切断積:貪欲法""" + # n <= 3 のときは、必ず 1 を切り出す if n <= 3: return 1 * (n - 1) - # 貪欲的に 3 を切り出す、a は 3 の個数、b は余り + # 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする a, b = n // 3, n % 3 if b == 1: - # 余りが 1 の場合、1 * 3 のペアを 2 * 2 に変換 + # 余りが 1 のときは、1 * 3 を 2 * 2 に変える return int(math.pow(3, a - 1)) * 2 * 2 if b == 2: - # 余りが 2 の場合、何もしない + # 余りが 2 のときは、そのままにする return int(math.pow(3, a)) * 2 - # 余りが 0 の場合、何もしない + # 余りが 0 のときは、そのままにする return int(math.pow(3, a)) -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": n = 58 - # 貪欲アルゴリズム + # 貪欲法 res = max_product_cutting(n) - print(f"切断の最大積は {res}") \ No newline at end of file + print(f"最大分割積は {res}") diff --git a/ja/codes/python/chapter_hashing/array_hash_map.py b/ja/codes/python/chapter_hashing/array_hash_map.py index f09456a62..4bd9800f1 100644 --- a/ja/codes/python/chapter_hashing/array_hash_map.py +++ b/ja/codes/python/chapter_hashing/array_hash_map.py @@ -6,7 +6,7 @@ Author: msk397 (machangxinq@gmail.com) class Pair: - """キー値ペア""" + """キーと値の組""" def __init__(self, key: int, val: str): self.key = key @@ -14,11 +14,11 @@ class Pair: class ArrayHashMap: - """配列実装に基づくハッシュテーブル""" + """配列ベースのハッシュテーブル""" def __init__(self): """コンストラクタ""" - # 100個のバケットを含む配列を初期化 + # 100 個のバケットを含む配列を初期化 self.buckets: list[Pair | None] = [None] * 100 def hash_func(self, key: int) -> int: @@ -26,8 +26,8 @@ class ArrayHashMap: index = key % 100 return index - def get(self, key: int) -> str: - """照会操作""" + def get(self, key: int) -> str | None: + """検索操作""" index: int = self.hash_func(key) pair: Pair = self.buckets[index] if pair is None: @@ -35,7 +35,7 @@ class ArrayHashMap: return pair.val def put(self, key: int, val: str): - """追加操作""" + """追加と更新の操作""" pair = Pair(key, val) index: int = self.hash_func(key) self.buckets[index] = pair @@ -43,11 +43,11 @@ class ArrayHashMap: def remove(self, key: int): """削除操作""" index: int = self.hash_func(key) - # None に設定し、削除を表現 + # None に設定し、削除を表す self.buckets[index] = None def entry_set(self) -> list[Pair]: - """すべてのキー値ペアを取得""" + """すべてのキーと値のペアを取得""" result: list[Pair] = [] for pair in self.buckets: if pair is not None: @@ -83,35 +83,35 @@ if __name__ == "__main__": hmap = ArrayHashMap() # 追加操作 - # キー値ペア (key, value) をハッシュテーブルに追加 - hmap.put(12836, "Ha") - hmap.put(15937, "Luo") - hmap.put(16750, "Suan") - hmap.put(13276, "Fa") - hmap.put(10583, "Ya") - print("\n追加後、ハッシュテーブルは\nKey -> Value") + # ハッシュテーブルにキーと値の組 (key, value) を追加する + hmap.put(12836, "シャオハー") + hmap.put(15937, "シャオルオ") + hmap.put(16750, "シャオスワン") + hmap.put(13276, "シャオファー") + hmap.put(10583, "シャオヤー") + print("\n追加完了後、ハッシュテーブルは\nKey -> Value") hmap.print() - # 照会操作 - # ハッシュテーブルにキーを入力し、値を取得 + # 検索操作 + # ハッシュテーブルにキー key を入力し、値 value を取得する name = hmap.get(15937) - print("\n学生ID 15937 を入力、名前 " + name + " が見つかりました") + print("\n学籍番号 15937 を入力すると、氏名は " + name) # 削除操作 - # ハッシュテーブルからキー値ペア (key, value) を削除 + # ハッシュテーブルからキーと値の組 (key, value) を削除する hmap.remove(10583) - print("\n10583 を削除後、ハッシュテーブルは\nKey -> Value") + print("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value") hmap.print() # ハッシュテーブルを走査 - print("\nキー値ペアを走査 Key->Value") + print("\nキーと値のペア Key->Value を走査") for pair in hmap.entry_set(): print(pair.key, "->", pair.val) - print("\nキーを個別に走査 Key") + print("\nキー Key を個別に走査") for key in hmap.key_set(): print(key) - print("\n値を個別に走査 Value") + print("\n値 Value を個別に走査") for val in hmap.value_set(): - print(val) \ No newline at end of file + print(val) diff --git a/ja/codes/python/chapter_hashing/built_in_hash.py b/ja/codes/python/chapter_hashing/built_in_hash.py index d8d624a70..b702598fa 100644 --- a/ja/codes/python/chapter_hashing/built_in_hash.py +++ b/ja/codes/python/chapter_hashing/built_in_hash.py @@ -24,14 +24,14 @@ if __name__ == "__main__": hash_dec = hash(dec) print(f"小数 {dec} のハッシュ値は {hash_dec}") - str = "Hello algorithm" + str = "Hello アルゴリズム" hash_str = hash(str) print(f"文字列 {str} のハッシュ値は {hash_str}") - tup = (12836, "Ha") + tup = (12836, "シャオハー") hash_tup = hash(tup) print(f"タプル {tup} のハッシュ値は {hash(hash_tup)}") obj = ListNode(0) hash_obj = hash(obj) - print(f"ノードオブジェクト {obj} のハッシュ値は {hash_obj}") \ No newline at end of file + print(f"ノードオブジェクト {obj} のハッシュ値は {hash_obj}") diff --git a/ja/codes/python/chapter_hashing/hash_map.py b/ja/codes/python/chapter_hashing/hash_map.py index 4491b45c9..557118fd3 100644 --- a/ja/codes/python/chapter_hashing/hash_map.py +++ b/ja/codes/python/chapter_hashing/hash_map.py @@ -16,35 +16,35 @@ if __name__ == "__main__": hmap = dict[int, str]() # 追加操作 - # キー値ペア (key, value) をハッシュテーブルに追加 - hmap[12836] = "Ha" - hmap[15937] = "Luo" - hmap[16750] = "Suan" - hmap[13276] = "Fa" - hmap[10583] = "Ya" - print("\n追加後、ハッシュテーブルは\nKey -> Value") + # ハッシュテーブルにキーと値の組 (key, value) を追加する + hmap[12836] = "シャオハー" + hmap[15937] = "シャオルオ" + hmap[16750] = "シャオスワン" + hmap[13276] = "シャオファー" + hmap[10583] = "シャオヤー" + print("\n追加完了後、ハッシュテーブルは\nKey -> Value") print_dict(hmap) - # 照会操作 - # ハッシュテーブルにキーを入力し、値を取得 + # 検索操作 + # ハッシュテーブルにキー key を入力し、値 value を取得する name: str = hmap[15937] - print("\n学生ID 15937 を入力、名前 " + name + " が見つかりました") + print("\n学籍番号 15937 を入力すると、氏名は " + name) # 削除操作 - # ハッシュテーブルからキー値ペア (key, value) を削除 + # ハッシュテーブルからキーと値の組 (key, value) を削除する hmap.pop(10583) - print("\n10583 を削除後、ハッシュテーブルは\nKey -> Value") + print("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value") print_dict(hmap) # ハッシュテーブルを走査 - print("\nキー値ペアを走査 Key->Value") + print("\nキーと値のペア Key->Value を走査") for key, value in hmap.items(): print(key, "->", value) - print("\nキーを個別に走査 Key") + print("\nキー Key を個別に走査") for key in hmap.keys(): print(key) - print("\n値を個別に走査 Value") + print("\n値 Value を個別に走査") for val in hmap.values(): - print(val) \ No newline at end of file + print(val) diff --git a/ja/codes/python/chapter_hashing/hash_map_chaining.py b/ja/codes/python/chapter_hashing/hash_map_chaining.py index 803fac5f2..c48745d3b 100644 --- a/ja/codes/python/chapter_hashing/hash_map_chaining.py +++ b/ja/codes/python/chapter_hashing/hash_map_chaining.py @@ -12,14 +12,14 @@ from chapter_hashing.array_hash_map import Pair class HashMapChaining: - """チェーンアドレス法ハッシュテーブル""" + """チェイン法ハッシュテーブル""" def __init__(self): """コンストラクタ""" - self.size = 0 # キー値ペアの数 - self.capacity = 4 # ハッシュテーブルの容量 - self.load_thres = 2.0 / 3.0 # 拡張をトリガーする負荷率の閾値 - self.extend_ratio = 2 # 拡張の倍数 + self.size = 0 # キーと値のペア数 + self.capacity = 4 # ハッシュテーブル容量 + self.load_thres = 2.0 / 3.0 # リサイズを発動する負荷率のしきい値 + self.extend_ratio = 2 # 拡張倍率 self.buckets = [[] for _ in range(self.capacity)] # バケット配列 def hash_func(self, key: int) -> int: @@ -31,29 +31,29 @@ class HashMapChaining: return self.size / self.capacity def get(self, key: int) -> str | None: - """照会操作""" + """検索操作""" index = self.hash_func(key) bucket = self.buckets[index] - # バケットを走査し、キーが見つかれば対応する val を返す + # バケットを走査し、key が見つかれば対応する val を返す for pair in bucket: if pair.key == key: return pair.val - # キーが見つからない場合、None を返す + # key が見つからない場合は None を返す return None def put(self, key: int, val: str): """追加操作""" - # 負荷率が閾値を超えた場合、拡張を実行 + # 負荷率がしきい値を超えたら、リサイズを実行 if self.load_factor() > self.load_thres: self.extend() index = self.hash_func(key) bucket = self.buckets[index] - # バケットを走査し、指定されたキーに遭遇した場合、対応する val を更新して返す + # バケットを走査し、指定した key が見つかれば対応する val を更新して返す for pair in bucket: if pair.key == key: pair.val = val return - # キーが見つからない場合、キー値ペアを末尾に追加 + # その key が存在しなければ、キーと値のペアを末尾に追加 pair = Pair(key, val) bucket.append(pair) self.size += 1 @@ -62,7 +62,7 @@ class HashMapChaining: """削除操作""" index = self.hash_func(key) bucket = self.buckets[index] - # バケットを走査し、その中からキー値ペアを削除 + # バケットを走査してキーと値のペアを削除 for pair in bucket: if pair.key == key: bucket.remove(pair) @@ -71,13 +71,13 @@ class HashMapChaining: def extend(self): """ハッシュテーブルを拡張""" - # 元のハッシュテーブルを一時的に保存 + # 元のハッシュテーブルを一時保存 buckets = self.buckets - # 拡張された新しいハッシュテーブルを初期化 + # リサイズ後の新しいハッシュテーブルを初期化 self.capacity *= self.extend_ratio self.buckets = [[] for _ in range(self.capacity)] self.size = 0 - # 元のハッシュテーブルから新しいハッシュテーブルにキー値ペアを移動 + # キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for bucket in buckets: for pair in bucket: self.put(pair.key, pair.val) @@ -97,22 +97,22 @@ if __name__ == "__main__": hashmap = HashMapChaining() # 追加操作 - # キー値ペア (key, value) をハッシュテーブルに追加 - hashmap.put(12836, "Ha") - hashmap.put(15937, "Luo") - hashmap.put(16750, "Suan") - hashmap.put(13276, "Fa") - hashmap.put(10583, "Ya") - print("\n追加後、ハッシュテーブルは\n[Key1 -> Value1, Key2 -> Value2, ...]") + # ハッシュテーブルにキーと値の組 (key, value) を追加する + hashmap.put(12836, "シャオハー") + hashmap.put(15937, "シャオルオ") + hashmap.put(16750, "シャオスワン") + hashmap.put(13276, "シャオファー") + hashmap.put(10583, "シャオヤー") + print("\n追加完了後、ハッシュテーブルは\n[Key1 -> Value1, Key2 -> Value2, ...]") hashmap.print() - # 照会操作 - # ハッシュテーブルにキーを入力し、値を取得 + # 検索操作 + # ハッシュテーブルにキー key を入力し、値 value を取得する name = hashmap.get(13276) - print("\n学生ID 13276 を入力、名前 " + name + " が見つかりました") + print("\n学籍番号 13276 を入力すると、氏名は " + name) # 削除操作 - # ハッシュテーブルからキー値ペア (key, value) を削除 + # ハッシュテーブルからキーと値の組 (key, value) を削除する hashmap.remove(12836) - print("\n12836 を削除後、ハッシュテーブルは\n[Key1 -> Value1, Key2 -> Value2, ...]") - hashmap.print() \ No newline at end of file + print("\n12836 を削除した後、ハッシュテーブルは\n[Key1 -> Value1, Key2 -> Value2, ...]") + hashmap.print() diff --git a/ja/codes/python/chapter_hashing/hash_map_open_addressing.py b/ja/codes/python/chapter_hashing/hash_map_open_addressing.py index 43347084a..9187f36a3 100644 --- a/ja/codes/python/chapter_hashing/hash_map_open_addressing.py +++ b/ja/codes/python/chapter_hashing/hash_map_open_addressing.py @@ -16,12 +16,12 @@ class HashMapOpenAddressing: def __init__(self): """コンストラクタ""" - self.size = 0 # キー値ペアの数 - self.capacity = 4 # ハッシュテーブルの容量 - self.load_thres = 2.0 / 3.0 # 拡張をトリガーする負荷率の閾値 - self.extend_ratio = 2 # 拡張の倍数 + self.size = 0 # キーと値のペア数 + self.capacity = 4 # ハッシュテーブル容量 + self.load_thres = 2.0 / 3.0 # リサイズを発動する負荷率のしきい値 + self.extend_ratio = 2 # 拡張倍率 self.buckets: list[Pair | None] = [None] * self.capacity # バケット配列 - self.TOMBSTONE = Pair(-1, "-1") # 削除マーク + self.TOMBSTONE = Pair(-1, "-1") # 削除済みマーク def hash_func(self, key: int) -> int: """ハッシュ関数""" @@ -32,70 +32,70 @@ class HashMapOpenAddressing: return self.size / self.capacity def find_bucket(self, key: int) -> int: - """key に対応するバケットインデックスを検索""" + """key に対応するバケットインデックスを探す""" index = self.hash_func(key) first_tombstone = -1 - # 線形探査、空のバケットに遭遇したらブレーク + # 線形プロービングを行い、空バケットに達したら終了 while self.buckets[index] is not None: - # キーに遭遇した場合、対応するバケットインデックスを返す + # key が見つかったら、対応するバケットのインデックスを返す if self.buckets[index].key == key: - # 削除マークが以前に遭遇していた場合、キー値ペアをそのインデックスに移動 + # 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 if first_tombstone != -1: self.buckets[first_tombstone] = self.buckets[index] self.buckets[index] = self.TOMBSTONE - return first_tombstone # 移動されたバケットインデックスを返す - return index # バケットインデックスを返す - # 最初に遭遇した削除マークを記録 + return first_tombstone # 移動後のバケットインデックスを返す + return index # バケットのインデックスを返す + # 最初に見つかった削除マークを記録 if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE: first_tombstone = index - # バケットインデックスを計算、末尾を超えた場合は先頭に戻る + # バケットのインデックスを計算し、末尾を越えたら先頭に戻る index = (index + 1) % self.capacity - # キーが存在しない場合、挿入ポイントのインデックスを返す + # key が存在しない場合は追加位置のインデックスを返す return index if first_tombstone == -1 else first_tombstone def get(self, key: int) -> str: - """照会操作""" - # key に対応するバケットインデックスを検索 + """検索操作""" + # key に対応するバケットインデックスを探す index = self.find_bucket(key) - # キー値ペアが見つかれば、対応する val を返す + # キーと値の組が見つかったら、対応する val を返す if self.buckets[index] not in [None, self.TOMBSTONE]: return self.buckets[index].val - # キー値ペアが存在しない場合、None を返す + # キーと値のペアが存在しない場合は `None` を返す return None def put(self, key: int, val: str): """追加操作""" - # 負荷率が閾値を超えた場合、拡張を実行 + # 負荷率がしきい値を超えたら、リサイズを実行 if self.load_factor() > self.load_thres: self.extend() - # key に対応するバケットインデックスを検索 + # key に対応するバケットインデックスを探す index = self.find_bucket(key) - # キー値ペアが見つかれば、val を上書きして返す + # キーと値の組が見つかったら、val を上書きして返す if self.buckets[index] not in [None, self.TOMBSTONE]: self.buckets[index].val = val return - # キー値ペアが存在しない場合、キー値ペアを追加 + # キーと値の組が存在しない場合は、その組を追加する self.buckets[index] = Pair(key, val) self.size += 1 def remove(self, key: int): """削除操作""" - # key に対応するバケットインデックスを検索 + # key に対応するバケットインデックスを探す index = self.find_bucket(key) - # キー値ペアが見つかれば、削除マークで覆う + # キーと値の組が見つかったら、削除マーカーで上書きする if self.buckets[index] not in [None, self.TOMBSTONE]: self.buckets[index] = self.TOMBSTONE self.size -= 1 def extend(self): """ハッシュテーブルを拡張""" - # 元のハッシュテーブルを一時的に保存 + # 元のハッシュテーブルを一時保存 buckets_tmp = self.buckets - # 拡張された新しいハッシュテーブルを初期化 + # リサイズ後の新しいハッシュテーブルを初期化 self.capacity *= self.extend_ratio self.buckets = [None] * self.capacity self.size = 0 - # 元のハッシュテーブルから新しいハッシュテーブルにキー値ペアを移動 + # キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す for pair in buckets_tmp: if pair not in [None, self.TOMBSTONE]: self.put(pair.key, pair.val) @@ -117,22 +117,22 @@ if __name__ == "__main__": hashmap = HashMapOpenAddressing() # 追加操作 - # キー値ペア (key, val) をハッシュテーブルに追加 - hashmap.put(12836, "Ha") - hashmap.put(15937, "Luo") - hashmap.put(16750, "Suan") - hashmap.put(13276, "Fa") - hashmap.put(10583, "Ya") - print("\n追加後、ハッシュテーブルは\nKey -> Value") + # ハッシュテーブルにキーと値の組 (key, val) を追加する + hashmap.put(12836, "シャオハー") + hashmap.put(15937, "シャオルオ") + hashmap.put(16750, "シャオスワン") + hashmap.put(13276, "シャオファー") + hashmap.put(10583, "シャオヤー") + print("\n追加完了後、ハッシュテーブルは\nKey -> Value") hashmap.print() - # 照会操作 - # ハッシュテーブルにキーを入力し、値 val を取得 + # 検索操作 + # ハッシュテーブルにキー key を入力し、値 val を得る name = hashmap.get(13276) - print("\n学生ID 13276 を入力、名前 " + name + " が見つかりました") + print("\n学籍番号 13276 を入力すると、氏名は " + name) # 削除操作 - # ハッシュテーブルからキー値ペア (key, val) を削除 + # ハッシュテーブルからキーと値の組 (key, val) を削除する hashmap.remove(16750) - print("\n16750 を削除後、ハッシュテーブルは\nKey -> Value") - hashmap.print() \ No newline at end of file + print("\n16750 を削除した後、ハッシュテーブルは\nKey -> Value") + hashmap.print() diff --git a/ja/codes/python/chapter_hashing/simple_hash.py b/ja/codes/python/chapter_hashing/simple_hash.py index 9ccbc46c4..406b681dd 100644 --- a/ja/codes/python/chapter_hashing/simple_hash.py +++ b/ja/codes/python/chapter_hashing/simple_hash.py @@ -6,7 +6,7 @@ Author: krahets (krahets@163.com) def add_hash(key: str) -> int: - """加法ハッシュ""" + """加算ハッシュ""" hash = 0 modulus = 1000000007 for c in key: @@ -15,7 +15,7 @@ def add_hash(key: str) -> int: def mul_hash(key: str) -> int: - """乗法ハッシュ""" + """乗算ハッシュ""" hash = 0 modulus = 1000000007 for c in key: @@ -24,7 +24,7 @@ def mul_hash(key: str) -> int: def xor_hash(key: str) -> int: - """XORハッシュ""" + """XOR ハッシュ""" hash = 0 modulus = 1000000007 for c in key: @@ -43,16 +43,16 @@ def rot_hash(key: str) -> int: """Driver Code""" if __name__ == "__main__": - key = "Hello algorithm" + key = "Hello アルゴリズム" hash = add_hash(key) - print(f"加法ハッシュ値は {hash}") + print(f"加算ハッシュ値は {hash}") hash = mul_hash(key) - print(f"乗法ハッシュ値は {hash}") + print(f"乗算ハッシュ値は {hash}") hash = xor_hash(key) - print(f"XORハッシュ値は {hash}") + print(f"XOR ハッシュ値は {hash}") hash = rot_hash(key) - print(f"回転ハッシュ値は {hash}") \ No newline at end of file + print(f"回転ハッシュ値は {hash}") diff --git a/ja/codes/python/chapter_heap/heap.py b/ja/codes/python/chapter_heap/heap.py index c7ecba5c0..f028d4bac 100644 --- a/ja/codes/python/chapter_heap/heap.py +++ b/ja/codes/python/chapter_heap/heap.py @@ -14,41 +14,41 @@ import heapq def test_push(heap: list, val: int, flag: int = 1): - heapq.heappush(heap, flag * val) # ヒープに要素をプッシュ - print(f"\n要素 {val} をヒープにプッシュ後") + heapq.heappush(heap, flag * val) # 要素をヒープに追加 + print(f"\n要素 {val} をヒープに追加した後") print_heap([flag * val for val in heap]) def test_pop(heap: list, flag: int = 1): - val = flag * heapq.heappop(heap) # ヒープの先頭要素をポップ - print(f"\nヒープの先頭要素 {val} がヒープから出た後") + val = flag * heapq.heappop(heap) # ヒープ頂点の要素を取り出す + print(f"\nヒープ先頭要素 {val} を取り出した後") print_heap([flag * val for val in heap]) -"""ドライバコード""" +"""Driver Code""" if __name__ == "__main__": # 最小ヒープを初期化 min_heap, flag = [], 1 # 最大ヒープを初期化 max_heap, flag = [], -1 - print("\n以下のテストケースは最大ヒープ用です") - # PythonのheapqモジュールはデフォルトでMinHeapを実装 - # ヒープに入れる前に「要素を反転」することを考慮し、比較演算子を逆転させて最大ヒープを実装 - # この例では、flag = 1は最小ヒープに対応し、flag = -1は最大ヒープに対応 + print("\n以下のテストケースは最大ヒープ") + # Python の heapq モジュールはデフォルトで最小ヒープを実装している + # 要素を負にしてからヒープに入れると大小関係を反転でき、最大ヒープを実現できる + # この例では、flag = 1 が最小ヒープ、flag = -1 が最大ヒープに対応する - # ヒープに要素をプッシュ + # 要素をヒープに追加 test_push(max_heap, 1, flag) test_push(max_heap, 3, flag) test_push(max_heap, 2, flag) test_push(max_heap, 5, flag) test_push(max_heap, 4, flag) - # ヒープの先頭要素にアクセス + # ヒープ頂点の要素を取得 peek: int = flag * max_heap[0] - print(f"\nヒープの先頭要素は {peek}") + print(f"\nヒープ先頭要素は {peek}") - # ヒープの先頭要素をポップ + # ヒープ頂点の要素を取り出す test_pop(max_heap, flag) test_pop(max_heap, flag) test_pop(max_heap, flag) @@ -57,15 +57,15 @@ if __name__ == "__main__": # ヒープのサイズを取得 size: int = len(max_heap) - print(f"\nヒープの要素数は {size}") + print(f"\nヒープ要素数は {size}") # ヒープが空かどうかを判定 is_empty: bool = not max_heap - print(f"\nヒープは空ですか {is_empty}") + print(f"\nヒープが空かどうかは {is_empty}") - # リストを入力してヒープを構築 - # 時間複雑度はO(n)、O(nlogn)ではない + # リストを入力してヒープを構築する + # 時間計算量は O(n) であり、O(nlogn) ではない min_heap = [1, 3, 2, 5, 4] heapq.heapify(min_heap) - print("\nリストを入力して最小ヒープを構築") - print_heap(min_heap) \ No newline at end of file + print("\nリストを入力して最小ヒープを構築した後") + print_heap(min_heap) diff --git a/ja/codes/python/chapter_heap/my_heap.py b/ja/codes/python/chapter_heap/my_heap.py index e0a9c71d1..ab75ea789 100644 --- a/ja/codes/python/chapter_heap/my_heap.py +++ b/ja/codes/python/chapter_heap/my_heap.py @@ -15,24 +15,24 @@ class MaxHeap: """最大ヒープ""" def __init__(self, nums: list[int]): - """コンストラクタ、入力リストに基づいてヒープを構築""" - # すべてのリスト要素をヒープに追加 + """コンストラクタ。入力リストに基づいてヒープを構築する""" + # リスト要素をそのままヒープに追加 self.max_heap = nums - # 葉以外のすべてのノードをヒープ化 + # 葉ノード以外のすべてのノードをヒープ化 for i in range(self.parent(self.size() - 1), -1, -1): self.sift_down(i) def left(self, i: int) -> int: - """左の子ノードのインデックスを取得""" + """左子ノードのインデックスを取得""" return 2 * i + 1 def right(self, i: int) -> int: - """右の子ノードのインデックスを取得""" + """右子ノードのインデックスを取得""" return 2 * i + 2 def parent(self, i: int) -> int: """親ノードのインデックスを取得""" - return (i - 1) // 2 # 整数除算で切り下げ + return (i - 1) // 2 # 切り捨て除算 def swap(self, i: int, j: int): """要素を交換""" @@ -47,91 +47,91 @@ class MaxHeap: return self.size() == 0 def peek(self) -> int: - """ヒープの先頭要素にアクセス""" + """ヒープ先頭要素にアクセス""" return self.max_heap[0] def push(self, val: int): - """ヒープに要素をプッシュ""" + """要素をヒープに追加""" # ノードを追加 self.max_heap.append(val) # 下から上へヒープ化 self.sift_up(self.size() - 1) def sift_up(self, i: int): - """ノードiから開始して、下から上へヒープ化""" + """ノード i から始めて、下から上へヒープ化""" while True: - # ノードiの親ノードを取得 + # ノード i の親ノードを取得 p = self.parent(i) - # 「ルートノードを越える」または「ノードが修復不要」の場合、ヒープ化を終了 + # 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 if p < 0 or self.max_heap[i] <= self.max_heap[p]: break - # 2つのノードを交換 + # 2 つのノードを交換 self.swap(i, p) - # 上向きのループヒープ化 + # ループで下から上へヒープ化 i = p def pop(self) -> int: - """要素をヒープから出す""" - # 空の処理 + """要素をヒープから取り出す""" + # 空判定の処理 if self.is_empty(): - raise IndexError("Heap is empty") - # ルートノードと最右端の葉ノードを交換(最初の要素と最後の要素を交換) + raise IndexError("ヒープが空です") + # 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) self.swap(0, self.size() - 1) # ノードを削除 val = self.max_heap.pop() # 上から下へヒープ化 self.sift_down(0) - # ヒープの先頭要素を返す + # ヒープ先頭要素を返す return val def sift_down(self, i: int): - """ノードiから開始して、上から下へヒープ化""" + """ノード i から始めて、上から下へヒープ化""" while True: - # i、l、rの中で最大のノードを決定し、maとする + # ノード i, l, r のうち値が最大のノードを ma とする l, r, ma = self.left(i), self.right(i), i if l < self.size() and self.max_heap[l] > self.max_heap[ma]: ma = l if r < self.size() and self.max_heap[r] > self.max_heap[ma]: ma = r - # ノードiが最大またはインデックスl、rが範囲外の場合、さらなるヒープ化は不要、ブレーク + # ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if ma == i: break - # 2つのノードを交換 + # 2 つのノードを交換 self.swap(i, ma) - # 下向きのループヒープ化 + # ループで上から下へヒープ化 i = ma def print(self): - """ヒープを出力(二分木)""" + """ヒープ(二分木)を出力""" print_heap(self.max_heap) -"""ドライバコード""" +"""Driver Code""" if __name__ == "__main__": # 最大ヒープを初期化 max_heap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) - print("\nリストを入力してヒープを構築") + print("\nリストを入力してヒープを構築した後") max_heap.print() - # ヒープの先頭要素にアクセス + # ヒープ頂点の要素を取得 peek = max_heap.peek() - print(f"\nヒープの先頭要素は {peek}") + print(f"\nヒープ先頭要素は {peek}") - # ヒープに要素をプッシュ + # 要素をヒープに追加 val = 7 max_heap.push(val) - print(f"\n要素 {val} をヒープにプッシュ後") + print(f"\n要素 {val} をヒープに追加した後") max_heap.print() - # ヒープの先頭要素をポップ + # ヒープ頂点の要素を取り出す peek = max_heap.pop() - print(f"\nヒープの先頭要素 {peek} がヒープから出た後") + print(f"\nヒープ先頭要素 {peek} を取り出した後") max_heap.print() # ヒープのサイズを取得 size = max_heap.size() - print(f"\nヒープの要素数は {size}") + print(f"\nヒープ要素数は {size}") # ヒープが空かどうかを判定 is_empty = max_heap.is_empty() - print(f"\nヒープは空ですか {is_empty}") \ No newline at end of file + print(f"\nヒープが空かどうかは {is_empty}") diff --git a/ja/codes/python/chapter_heap/top_k.py b/ja/codes/python/chapter_heap/top_k.py index bdc386629..7f652a50d 100644 --- a/ja/codes/python/chapter_heap/top_k.py +++ b/ja/codes/python/chapter_heap/top_k.py @@ -14,26 +14,26 @@ import heapq def top_k_heap(nums: list[int], k: int) -> list[int]: - """ヒープを使用して配列内の最大k個の要素を見つける""" + """ヒープに基づいて配列中の最大の k 個の要素を探す""" # 最小ヒープを初期化 heap = [] - # 配列の最初のk個の要素をヒープに入力 + # 配列の先頭 k 個の要素をヒープに追加 for i in range(k): heapq.heappush(heap, nums[i]) - # k+1番目の要素から、ヒープの長さをkに保つ + # k+1 番目の要素から開始し、ヒープ長を k に保つ for i in range(k, len(nums)): - # 現在の要素がヒープの先頭要素より大きい場合、ヒープの先頭要素を削除し、現在の要素をヒープに入力 + # 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する if nums[i] > heap[0]: heapq.heappop(heap) heapq.heappush(heap, nums[i]) return heap -"""ドライバコード""" +"""Driver Code""" if __name__ == "__main__": nums = [1, 7, 6, 3, 2] k = 3 res = top_k_heap(nums, k) print(f"最大の {k} 個の要素は") - print_heap(res) \ No newline at end of file + print_heap(res) diff --git a/ja/codes/python/chapter_searching/binary_search.py b/ja/codes/python/chapter_searching/binary_search.py index 0636496cc..563d59cc4 100644 --- a/ja/codes/python/chapter_searching/binary_search.py +++ b/ja/codes/python/chapter_searching/binary_search.py @@ -6,47 +6,47 @@ Author: timi (xisunyy@163.com) def binary_search(nums: list[int], target: int) -> int: - """二分探索(両端閉区間)""" - # 両端閉区間 [0, n-1] を初期化、すなわち i, j はそれぞれ配列の最初の要素と最後の要素を指す + """二分探索(両閉区間)""" + # 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す i, j = 0, len(nums) - 1 - # 検索区間が空になるまでループ(i > j のとき空) + # ループし、探索区間が空になったら終了する(i > j で空) while i <= j: - # 理論的には、Pythonの数値は無限に大きくなることができる(メモリサイズに依存)ため、大きな数のオーバーフローを考慮する必要はない - m = i + (j - i) // 2 # 中点インデックス m を計算 + # 理論上、Python の数値は無限に大きくできるため(メモリ容量に依存)、大きな数のオーバーフローを考慮する必要はない + m = (i + j) // 2 # 中点インデックス m を計算 if nums[m] < target: - i = m + 1 # この場合、target は区間 [m+1, j] にあることを示す + i = m + 1 # この場合、target は区間 [m+1, j] にある elif nums[m] > target: - j = m - 1 # この場合、target は区間 [i, m-1] にあることを示す + j = m - 1 # この場合、target は区間 [i, m-1] にある else: - return m # ターゲット要素が見つかったため、そのインデックスを返す - return -1 # ターゲット要素が見つからなかったため、-1 を返す + return m # 目標要素が見つかったらそのインデックスを返す + return -1 # 目標要素が見つからなければ -1 を返す def binary_search_lcro(nums: list[int], target: int) -> int: """二分探索(左閉右開区間)""" - # 左閉右開区間 [0, n) を初期化、すなわち i, j はそれぞれ配列の最初の要素と最後の要素+1を指す + # 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す i, j = 0, len(nums) - # 検索区間が空になるまでループ(i = j のとき空) + # ループし、探索区間が空になったら終了する(i = j で空) while i < j: - m = i + (j - i) // 2 # 中点インデックス m を計算 + m = (i + j) // 2 # 中点インデックス m を計算 if nums[m] < target: - i = m + 1 # この場合、target は区間 [m+1, j) にあることを示す + i = m + 1 # この場合、target は区間 [m+1, j) にある elif nums[m] > target: - j = m # この場合、target は区間 [i, m) にあることを示す + j = m # この場合、target は区間 [i, m) にある else: - return m # ターゲット要素が見つかったため、そのインデックスを返す - return -1 # ターゲット要素が見つからなかったため、-1 を返す + return m # 目標要素が見つかったらそのインデックスを返す + return -1 # 目標要素が見つからなければ -1 を返す -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": target = 6 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] - # 二分探索(両端閉区間) + # 二分探索(両閉区間) index = binary_search(nums, target) - print("ターゲット要素 6 のインデックス =", index) + print("対象要素 6 のインデックス = ", index) # 二分探索(左閉右開区間) index = binary_search_lcro(nums, target) - print("ターゲット要素 6 のインデックス =", index) \ No newline at end of file + print("対象要素 6 のインデックス = ", index) diff --git a/ja/codes/python/chapter_searching/binary_search_edge.py b/ja/codes/python/chapter_searching/binary_search_edge.py index 703ddb563..6bf94c213 100644 --- a/ja/codes/python/chapter_searching/binary_search_edge.py +++ b/ja/codes/python/chapter_searching/binary_search_edge.py @@ -12,38 +12,38 @@ from binary_search_insertion import binary_search_insertion def binary_search_left_edge(nums: list[int], target: int) -> int: - """最左端のターゲットの二分探索""" - # ターゲットの挿入位置を見つけることと同等 + """最も左の target を二分探索""" + # target の挿入位置を探すのと等価 i = binary_search_insertion(nums, target) - # ターゲットが見つからなかった場合、-1 を返す + # target が見つからなければ、-1 を返す if i == len(nums) or nums[i] != target: return -1 - # ターゲットが見つかった場合、インデックス i を返す + # target が見つかったら、インデックス i を返す return i def binary_search_right_edge(nums: list[int], target: int) -> int: - """最右端のターゲットの二分探索""" - # 最左端のターゲット + 1 を見つけることに変換 + """最も右の target を二分探索""" + # 最左の target + 1 を探す問題に変換する i = binary_search_insertion(nums, target + 1) - # j は最右端のターゲットを指し、i はターゲットより大きい最初の要素を指す + # j は最も右の target を指し、i は target より大きい最初の要素を指す j = i - 1 - # ターゲットが見つからなかった場合、-1 を返す + # target が見つからなければ、-1 を返す if j == -1 or nums[j] != target: return -1 - # ターゲットが見つかった場合、インデックス j を返す + # target が見つかったら、インデックス j を返す return j -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": - # 重複要素のある配列 + # 重複要素を含む配列 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print(f"\n配列 nums = {nums}") - # 左端と右端の境界の二分探索 + # 二分探索で左端と右端を探す for target in [6, 7]: index = binary_search_left_edge(nums, target) - print(f"最左端の要素 {target} のインデックスは {index}") + print(f"左端の要素 {target} のインデックスは {index}") index = binary_search_right_edge(nums, target) - print(f"最右端の要素 {target} のインデックスは {index}") \ No newline at end of file + print(f"右端の要素 {target} のインデックスは {index}") diff --git a/ja/codes/python/chapter_searching/binary_search_insertion.py b/ja/codes/python/chapter_searching/binary_search_insertion.py index 0f48d6541..3934a742c 100644 --- a/ja/codes/python/chapter_searching/binary_search_insertion.py +++ b/ja/codes/python/chapter_searching/binary_search_insertion.py @@ -6,49 +6,49 @@ Author: krahets (krahets@163.com) def binary_search_insertion_simple(nums: list[int], target: int) -> int: - """挿入位置の二分探索(重複要素なし)""" - i, j = 0, len(nums) - 1 # 両端閉区間 [0, n-1] を初期化 + """二分探索で挿入位置を探す(重複要素なし)""" + i, j = 0, len(nums) - 1 # 両閉区間 [0, n-1] を初期化 while i <= j: - m = i + (j - i) // 2 # 中点インデックス m を計算 + m = (i + j) // 2 # 中点インデックス m を計算 if nums[m] < target: - i = m + 1 # ターゲットは区間 [m+1, j] にある + i = m + 1 # target は区間 [m+1, j] にある elif nums[m] > target: - j = m - 1 # ターゲットは区間 [i, m-1] にある + j = m - 1 # target は区間 [i, m-1] にある else: - return m # ターゲットが見つかった場合、挿入位置 m を返す - # ターゲットが見つからなかった場合、挿入位置 i を返す + return m # target が見つかったら、挿入位置 m を返す + # target が見つからなければ、挿入位置 i を返す return i def binary_search_insertion(nums: list[int], target: int) -> int: - """挿入位置の二分探索(重複要素あり)""" - i, j = 0, len(nums) - 1 # 両端閉区間 [0, n-1] を初期化 + """二分探索で挿入位置を探す(重複要素あり)""" + i, j = 0, len(nums) - 1 # 両閉区間 [0, n-1] を初期化 while i <= j: - m = i + (j - i) // 2 # 中点インデックス m を計算 + m = (i + j) // 2 # 中点インデックス m を計算 if nums[m] < target: - i = m + 1 # ターゲットは区間 [m+1, j] にある + i = m + 1 # target は区間 [m+1, j] にある elif nums[m] > target: - j = m - 1 # ターゲットは区間 [i, m-1] にある + j = m - 1 # target は区間 [i, m-1] にある else: - j = m - 1 # ターゲット未満の最初の要素は区間 [i, m-1] にある + j = m - 1 # target より小さい最初の要素は区間 [i, m-1] にある # 挿入位置 i を返す return i -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": # 重複要素のない配列 nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] print(f"\n配列 nums = {nums}") - # 挿入位置の二分探索 + # 二分探索で挿入位置を探す for target in [6, 9]: index = binary_search_insertion_simple(nums, target) - print(f"要素 {target} の挿入位置インデックスは {index}") + print(f"要素 {target} の挿入位置のインデックスは {index}") - # 重複要素のある配列 + # 重複要素を含む配列 nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] print(f"\n配列 nums = {nums}") - # 挿入位置の二分探索 + # 二分探索で挿入位置を探す for target in [2, 6, 20]: index = binary_search_insertion(nums, target) - print(f"要素 {target} の挿入位置インデックスは {index}") \ No newline at end of file + print(f"要素 {target} の挿入位置のインデックスは {index}") diff --git a/ja/codes/python/chapter_searching/hashing_search.py b/ja/codes/python/chapter_searching/hashing_search.py index ff6fffbb2..618e11da1 100644 --- a/ja/codes/python/chapter_searching/hashing_search.py +++ b/ja/codes/python/chapter_searching/hashing_search.py @@ -13,8 +13,8 @@ from modules import ListNode, list_to_linked_list def hashing_search_array(hmap: dict[int, int], target: int) -> int: """ハッシュ探索(配列)""" - # ハッシュテーブルのキー:ターゲット要素、値:インデックス - # ハッシュテーブルがこのキーを含まない場合、-1 を返す + # ハッシュテーブルの key: 目標要素、value: インデックス + # ハッシュテーブルにこの key がなければ -1 を返す return hmap.get(target, -1) @@ -22,12 +22,12 @@ def hashing_search_linkedlist( hmap: dict[int, ListNode], target: int ) -> ListNode | None: """ハッシュ探索(連結リスト)""" - # ハッシュテーブルのキー:ターゲット要素、値:ノードオブジェクト - # ハッシュテーブルがこのキーを含まない場合、None を返す + # ハッシュテーブルの key: 対象要素、value: ノードオブジェクト + # ハッシュテーブルにこの key がなければ None を返す return hmap.get(target, None) -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": target = 3 @@ -36,16 +36,16 @@ if __name__ == "__main__": # ハッシュテーブルを初期化 map0 = dict[int, int]() for i in range(len(nums)): - map0[nums[i]] = i # キー:要素、値:インデックス + map0[nums[i]] = i # key: 要素、value: インデックス index: int = hashing_search_array(map0, target) - print("ターゲット要素 3 のインデックス =", index) + print("対象要素 3 のインデックス =", index) # ハッシュ探索(連結リスト) head: ListNode = list_to_linked_list(nums) # ハッシュテーブルを初期化 map1 = dict[int, ListNode]() while head: - map1[head.val] = head # キー:ノード値、値:ノード + map1[head.val] = head # key: ノード値、value: ノード head = head.next node: ListNode = hashing_search_linkedlist(map1, target) - print("ターゲットノード値 3 に対応するノードオブジェクトは", node) \ No newline at end of file + print("対象ノード値 3 に対応するノードオブジェクトは", node) diff --git a/ja/codes/python/chapter_searching/linear_search.py b/ja/codes/python/chapter_searching/linear_search.py index 1fa74017f..424b15376 100644 --- a/ja/codes/python/chapter_searching/linear_search.py +++ b/ja/codes/python/chapter_searching/linear_search.py @@ -15,31 +15,31 @@ def linear_search_array(nums: list[int], target: int) -> int: """線形探索(配列)""" # 配列を走査 for i in range(len(nums)): - if nums[i] == target: # ターゲット要素が見つかったため、そのインデックスを返す + if nums[i] == target: # 目標要素が見つかったらそのインデックスを返す return i - return -1 # ターゲット要素が見つからなかったため、-1 を返す + return -1 # 目標要素が見つからなければ -1 を返す def linear_search_linkedlist(head: ListNode, target: int) -> ListNode | None: """線形探索(連結リスト)""" - # リストを走査 + # 連結リストを走査 while head: - if head.val == target: # ターゲットノードが見つかったため、それを返す + if head.val == target: # 対象ノードが見つかったら、それを返す return head head = head.next - return None # ターゲットノードが見つからなかったため、None を返す + return None # 対象ノードが見つからない場合は None を返す -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": target = 3 - # 配列での線形探索を実行 + # 配列で線形探索を行う nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] index: int = linear_search_array(nums, target) - print("ターゲット要素 3 のインデックス =", index) + print("対象要素 3 のインデックス =", index) - # 連結リストでの線形探索を実行 + # 連結リストで線形探索を行う head: ListNode = list_to_linked_list(nums) node: ListNode | None = linear_search_linkedlist(head, target) - print("ターゲットノード値 3 に対応するノードオブジェクトは", node) \ No newline at end of file + print("対象ノード値 3 に対応するノードオブジェクトは", node) diff --git a/ja/codes/python/chapter_searching/two_sum.py b/ja/codes/python/chapter_searching/two_sum.py index 3e6a44f70..b82b2cbf7 100644 --- a/ja/codes/python/chapter_searching/two_sum.py +++ b/ja/codes/python/chapter_searching/two_sum.py @@ -6,8 +6,8 @@ Author: krahets (krahets@163.com) def two_sum_brute_force(nums: list[int], target: int) -> list[int]: - """方法一:ブルートフォース列挙""" - # 二重ループ、時間計算量は O(n^2) + """方法 1:総当たり列挙""" + # 2重ループのため、時間計算量は O(n^2) for i in range(len(nums) - 1): for j in range(i + 1, len(nums)): if nums[i] + nums[j] == target: @@ -16,10 +16,10 @@ def two_sum_brute_force(nums: list[int], target: int) -> list[int]: def two_sum_hash_table(nums: list[int], target: int) -> list[int]: - """方法二:補助ハッシュテーブル""" - # 補助ハッシュテーブル、空間計算量は O(n) + """方法 2:補助ハッシュテーブル""" + # 補助ハッシュテーブルを使用し、空間計算量は O(n) dic = {} - # 単一ループ、時間計算量は O(n) + # 単一ループで、時間計算量は O(n) for i in range(len(nums)): if target - nums[i] in dic: return [dic[target - nums[i]], i] @@ -27,16 +27,16 @@ def two_sum_hash_table(nums: list[int], target: int) -> list[int]: return [] -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": - # ======= テストケース ======= + # ======= Test Case ======= nums = [2, 7, 11, 15] target = 13 - # ====== ドライバーコード ====== - # 方法一 + # ====== Driver Code ====== + # 方法 1 res: list[int] = two_sum_brute_force(nums, target) - print("方法一の結果 =", res) - # 方法二 + print("方法1 res =", res) + # 方法 2 res: list[int] = two_sum_hash_table(nums, target) - print("方法二の結果 =", res) \ No newline at end of file + print("方法2 res =", res) diff --git a/ja/codes/python/chapter_sorting/bubble_sort.py b/ja/codes/python/chapter_sorting/bubble_sort.py index 903604fff..df3a8d5b1 100644 --- a/ja/codes/python/chapter_sorting/bubble_sort.py +++ b/ja/codes/python/chapter_sorting/bubble_sort.py @@ -8,9 +8,9 @@ Author: timi (xisunyy@163.com) def bubble_sort(nums: list[int]): """バブルソート""" n = len(nums) - # 外側のループ:未ソート範囲は [0, i] + # 外側のループ:未ソート区間は [0, i] for i in range(n - 1, 0, -1): - # 内側のループ:未ソート範囲 [0, i] の最大要素を範囲の右端に移動 + # 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for j in range(i): if nums[j] > nums[j + 1]: # nums[j] と nums[j + 1] を交換 @@ -18,27 +18,27 @@ def bubble_sort(nums: list[int]): def bubble_sort_with_flag(nums: list[int]): - """バブルソート(フラグによる最適化)""" + """バブルソート(フラグ最適化)""" n = len(nums) - # 外側のループ:未ソート範囲は [0, i] + # 外側のループ:未ソート区間は [0, i] for i in range(n - 1, 0, -1): - flag = False # フラグを初期化 - # 内側のループ:未ソート範囲 [0, i] の最大要素を範囲の右端に移動 + flag = False # フラグを初期化する + # 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 for j in range(i): if nums[j] > nums[j + 1]: # nums[j] と nums[j + 1] を交換 nums[j], nums[j + 1] = nums[j + 1], nums[j] - flag = True # 要素を交換したことを記録 + flag = True # 交換する要素を記録 if not flag: - break # この回の「バブリング」で要素が交換されなかった場合、終了 + break # このバブル処理で要素交換が一度もなければそのまま終了 -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] bubble_sort(nums) - print("バブルソート完了 nums =", nums) + print("バブルソート完了後 nums =", nums) nums1 = [4, 1, 3, 1, 5, 2] bubble_sort_with_flag(nums1) - print("バブルソート完了 nums =", nums1) \ No newline at end of file + print("バブルソート完了後 nums =", nums1) diff --git a/ja/codes/python/chapter_sorting/bucket_sort.py b/ja/codes/python/chapter_sorting/bucket_sort.py index 8a482daa7..08088b7be 100644 --- a/ja/codes/python/chapter_sorting/bucket_sort.py +++ b/ja/codes/python/chapter_sorting/bucket_sort.py @@ -7,20 +7,20 @@ Author: krahets (krahets@163.com) def bucket_sort(nums: list[float]): """バケットソート""" - # k = n/2 個のバケットを初期化、各バケットに平均2個の要素を配置することを期待 + # k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする k = len(nums) // 2 buckets = [[] for _ in range(k)] - # 1. 配列要素を各バケットに分散 + # 1. 配列要素を各バケットに振り分ける for num in nums: - # 入力データ範囲は [0, 1)、num * k を使用してインデックス範囲 [0, k-1] にマッピング + # 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する i = int(num * k) # num をバケット i に追加 buckets[i].append(num) - # 2. 各バケットをソート + # 2. 各バケットをソートする for bucket in buckets: - # 組み込みソート関数を使用、他のソートアルゴリズムに置き換えることも可能 + # 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい bucket.sort() - # 3. バケットを走査して結果をマージ + # 3. バケットを走査して結果を結合 i = 0 for bucket in buckets: for num in bucket: @@ -29,7 +29,7 @@ def bucket_sort(nums: list[float]): if __name__ == "__main__": - # 入力データが浮動小数点数、範囲 [0, 1) であると仮定 + # 入力データは範囲 [0, 1) の浮動小数点数とする nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] bucket_sort(nums) - print("バケットソート完了 nums =", nums) \ No newline at end of file + print("バケットソート完了後 nums =", nums) diff --git a/ja/codes/python/chapter_sorting/counting_sort.py b/ja/codes/python/chapter_sorting/counting_sort.py index 2c0bda919..eb513b215 100644 --- a/ja/codes/python/chapter_sorting/counting_sort.py +++ b/ja/codes/python/chapter_sorting/counting_sort.py @@ -7,17 +7,15 @@ Author: krahets (krahets@163.com) def counting_sort_naive(nums: list[int]): """計数ソート""" - # シンプルな実装、オブジェクトのソートには使用できない - # 1. 配列内の最大要素 m を統計 - m = 0 - for num in nums: - m = max(m, num) - # 2. 各数字の出現回数を統計 + # 簡易版。オブジェクトのソートには使えない + # 1. 配列の最大要素 m を求める + m = max(nums) + # 2. 各数値の出現回数を数える # counter[num] は num の出現回数を表す counter = [0] * (m + 1) for num in nums: counter[num] += 1 - # 3. counter を走査し、各要素を元の配列 nums に埋め戻し + # 3. counter を走査し、各要素を元の配列 nums に書き戻す i = 0 for num in range(m + 1): for _ in range(counter[num]): @@ -27,38 +25,38 @@ def counting_sort_naive(nums: list[int]): def counting_sort(nums: list[int]): """計数ソート""" - # 完全な実装、オブジェクトのソートが可能で、安定ソート - # 1. 配列内の最大要素 m を統計 + # 完全版。オブジェクトをソートでき、かつ安定ソートである + # 1. 配列の最大要素 m を求める m = max(nums) - # 2. 各数字の出現回数を統計 + # 2. 各数値の出現回数を数える # counter[num] は num の出現回数を表す counter = [0] * (m + 1) for num in nums: counter[num] += 1 - # 3. counter の前置和を計算し、「出現回数」を「末尾インデックス」に変換 - # counter[num]-1 は res において num が最後に出現するインデックス + # 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する + # つまり counter[num]-1 は、num が res に最後に現れるインデックス for i in range(m): counter[i + 1] += counter[i] - # 4. nums を逆順に走査し、各要素を結果配列 res に配置 + # 4. nums を逆順に走査し、各要素を結果配列 res に格納する # 結果を記録するための配列 res を初期化 n = len(nums) res = [0] * n for i in range(n - 1, -1, -1): num = nums[i] res[counter[num] - 1] = num # num を対応するインデックスに配置 - counter[num] -= 1 # 前置和を1減らし、num を配置する次のインデックスを取得 - # 結果配列 res を使用して元の配列 nums を上書き + counter[num] -= 1 # 累積和を 1 減らして、次に num を配置するインデックスを得る + # 結果配列 res で元の配列 nums を上書きする for i in range(n): nums[i] = res[i] -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] counting_sort_naive(nums) - print(f"計数ソート(オブジェクトソート不可)完了 nums = {nums}") + print(f"計数ソート(オブジェクトはソート不可)完了後 nums = {nums}") nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] counting_sort(nums1) - print(f"計数ソート完了 nums1 = {nums1}") \ No newline at end of file + print(f"計数ソート完了後 nums1 = {nums1}") diff --git a/ja/codes/python/chapter_sorting/heap_sort.py b/ja/codes/python/chapter_sorting/heap_sort.py index 28127c2eb..c189d4aa8 100644 --- a/ja/codes/python/chapter_sorting/heap_sort.py +++ b/ja/codes/python/chapter_sorting/heap_sort.py @@ -6,9 +6,9 @@ Author: krahets (krahets@163.com) def sift_down(nums: list[int], n: int, i: int): - """ヒープの長さが n、ノード i から上から下へヒープ化を開始""" + """ヒープの長さは n。ノード i から下方向にヒープ化""" while True: - # i、l、r の中で最大のノードを判定し、ma とする + # ノード i, l, r のうち値が最大のノードを ma とする l = 2 * i + 1 r = 2 * i + 2 ma = i @@ -16,30 +16,30 @@ def sift_down(nums: list[int], n: int, i: int): ma = l if r < n and nums[r] > nums[ma]: ma = r - # ノード i が最大または l、r のインデックスが範囲外の場合、さらなるヒープ化は不要、ループを抜ける + # ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける if ma == i: break - # 2つのノードを交換 + # 2 つのノードを交換 nums[i], nums[ma] = nums[ma], nums[i] - # 下向きにヒープ化をループ + # ループで上から下へヒープ化 i = ma def heap_sort(nums: list[int]): """ヒープソート""" - # ヒープ構築操作:葉ノード以外のすべてのノードをヒープ化 + # ヒープ構築:葉ノード以外のすべてのノードをヒープ化する for i in range(len(nums) // 2 - 1, -1, -1): sift_down(nums, len(nums), i) - # ヒープから最大要素を抽出し、n-1 回繰り返す + # ヒープから最大要素を取り出し、n-1 回繰り返す for i in range(len(nums) - 1, 0, -1): - # ルートノードと最も右の葉ノードを交換(最初の要素と最後の要素を交換) + # 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) nums[0], nums[i] = nums[i], nums[0] - # ルートノードから上から下へヒープ化を開始 + # 根ノードを起点に、上から下へヒープ化 sift_down(nums, i, 0) -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] heap_sort(nums) - print("ヒープソート完了 nums =", nums) \ No newline at end of file + print("ヒープソート完了後 nums =", nums) diff --git a/ja/codes/python/chapter_sorting/insertion_sort.py b/ja/codes/python/chapter_sorting/insertion_sort.py index 0f11349d4..3e020db65 100644 --- a/ja/codes/python/chapter_sorting/insertion_sort.py +++ b/ja/codes/python/chapter_sorting/insertion_sort.py @@ -7,19 +7,19 @@ Author: timi (xisunyy@163.com) def insertion_sort(nums: list[int]): """挿入ソート""" - # 外側のループ:ソート済み範囲は [0, i-1] + # 外側ループ:整列済み区間は [0, i-1] for i in range(1, len(nums)): base = nums[i] j = i - 1 - # 内側のループ:base をソート済み範囲 [0, i-1] の正しい位置に挿入 + # 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する while j >= 0 and nums[j] > base: - nums[j + 1] = nums[j] # nums[j] を右に1つ移動 + nums[j + 1] = nums[j] # nums[j] を 1 つ右へ移動する j -= 1 - nums[j + 1] = base # base を正しい位置に代入 + nums[j + 1] = base # base を正しい位置に配置する -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] insertion_sort(nums) - print("挿入ソート完了 nums =", nums) \ No newline at end of file + print("挿入ソート完了後 nums =", nums) diff --git a/ja/codes/python/chapter_sorting/merge_sort.py b/ja/codes/python/chapter_sorting/merge_sort.py index 0d6d3b663..67f917a70 100644 --- a/ja/codes/python/chapter_sorting/merge_sort.py +++ b/ja/codes/python/chapter_sorting/merge_sort.py @@ -6,13 +6,13 @@ Author: timi (xisunyy@163.com), krahets (krahets@163.com) def merge(nums: list[int], left: int, mid: int, right: int): - """左サブ配列と右サブ配列をマージ""" - # 左サブ配列区間は [left, mid]、右サブ配列区間は [mid+1, right] - # 一時配列 tmp を作成してマージ結果を格納 + """左部分配列と右部分配列をマージ""" + # 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] + # マージ結果を格納する一時配列 tmp を作成 tmp = [0] * (right - left + 1) - # 左右サブ配列の開始インデックスを初期化 + # 左右の部分配列の開始インデックスを初期化する i, j, k = left, mid + 1, 0 - # 両方のサブ配列に要素が残っている間、より小さい要素を一時配列にコピー + # 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする while i <= mid and j <= right: if nums[i] <= nums[j]: tmp[k] = nums[i] @@ -21,7 +21,7 @@ def merge(nums: list[int], left: int, mid: int, right: int): tmp[k] = nums[j] j += 1 k += 1 - # 残った左右サブ配列の要素を一時配列にコピー + # 左右の部分配列の残り要素を一時配列にコピーする while i <= mid: tmp[k] = nums[i] i += 1 @@ -30,7 +30,7 @@ def merge(nums: list[int], left: int, mid: int, right: int): tmp[k] = nums[j] j += 1 k += 1 - # 一時配列 tmp の要素を元の配列 nums の対応する区間にコピーバック + # 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする for k in range(0, len(tmp)): nums[left + k] = tmp[k] @@ -39,17 +39,17 @@ def merge_sort(nums: list[int], left: int, right: int): """マージソート""" # 終了条件 if left >= right: - return # サブ配列の長さが1のときに再帰を終了 - # 分割段階 - mid = left + (right - left) // 2 # 中点を計算 - merge_sort(nums, left, mid) # 左サブ配列を再帰的に処理 - merge_sort(nums, mid + 1, right) # 右サブ配列を再帰的に処理 - # マージ段階 + return # 部分配列の長さが 1 になったら再帰を終了 + # 分割フェーズ + mid = (left + right) // 2 # 中点を計算 + merge_sort(nums, left, mid) # 左部分配列を再帰処理 + merge_sort(nums, mid + 1, right) # 右部分配列を再帰処理 + # マージフェーズ merge(nums, left, mid, right) -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": nums = [7, 3, 2, 6, 0, 1, 5, 4] merge_sort(nums, 0, len(nums) - 1) - print("マージソート完了 nums =", nums) \ No newline at end of file + print("マージソート完了後 nums =", nums) diff --git a/ja/codes/python/chapter_sorting/quick_sort.py b/ja/codes/python/chapter_sorting/quick_sort.py index 2582790f7..0ae12a9d0 100644 --- a/ja/codes/python/chapter_sorting/quick_sort.py +++ b/ja/codes/python/chapter_sorting/quick_sort.py @@ -9,28 +9,28 @@ class QuickSort: """クイックソートクラス""" def partition(self, nums: list[int], left: int, right: int) -> int: - """分割""" - # nums[left] をピボットとして使用 + """番兵分割""" + # nums[left] を基準値とする i, j = left, right while i < j: while i < j and nums[j] >= nums[left]: - j -= 1 # 右から左へピボットより小さい最初の要素を探す + j -= 1 # 右から左へ基準値未満の最初の要素を探す while i < j and nums[i] <= nums[left]: - i += 1 # 左から右へピボットより大きい最初の要素を探す - # 要素を交換 + i += 1 # 左から右へ基準値より大きい最初の要素を探す + # 要素の交換 nums[i], nums[j] = nums[j], nums[i] - # ピボットを2つのサブ配列の境界に交換 + # 基準値を 2 つの部分配列の境界へ交換する nums[i], nums[left] = nums[left], nums[i] - return i # ピボットのインデックスを返す + return i # 基準値のインデックスを返す def quick_sort(self, nums: list[int], left: int, right: int): """クイックソート""" - # サブ配列の長さが1のときに再帰を終了 + # 部分配列の長さが 1 なら再帰を終了する if left >= right: return - # 分割 + # 番兵分割 pivot = self.partition(nums, left, right) - # 左サブ配列と右サブ配列を再帰的に処理 + # 左右の部分配列を再帰処理 self.quick_sort(nums, left, pivot - 1) self.quick_sort(nums, pivot + 1, right) @@ -39,7 +39,7 @@ class QuickSortMedian: """クイックソートクラス(中央値ピボット最適化)""" def median_three(self, nums: list[int], left: int, mid: int, right: int) -> int: - """3つの候補要素の中央値を選択""" + """3つの候補要素の中央値を選ぶ""" l, m, r = nums[left], nums[mid], nums[right] if (l <= m <= r) or (r <= m <= l): return mid # m は l と r の間 @@ -48,82 +48,82 @@ class QuickSortMedian: return right def partition(self, nums: list[int], left: int, right: int) -> int: - """分割(三点中央値)""" - # nums[left] をピボットとして使用 + """番兵による分割処理(3 点中央値)""" + # nums[left] を基準値とする med = self.median_three(nums, left, (left + right) // 2, right) - # 中央値を配列の最左端に交換 + # 中央値を配列の最左端に交換する nums[left], nums[med] = nums[med], nums[left] - # nums[left] をピボットとして使用 + # nums[left] を基準値とする i, j = left, right while i < j: while i < j and nums[j] >= nums[left]: - j -= 1 # 右から左へピボットより小さい最初の要素を探す + j -= 1 # 右から左へ基準値未満の最初の要素を探す while i < j and nums[i] <= nums[left]: - i += 1 # 左から右へピボットより大きい最初の要素を探す - # 要素を交換 + i += 1 # 左から右へ基準値より大きい最初の要素を探す + # 要素の交換 nums[i], nums[j] = nums[j], nums[i] - # ピボットを2つのサブ配列の境界に交換 + # 基準値を 2 つの部分配列の境界へ交換する nums[i], nums[left] = nums[left], nums[i] - return i # ピボットのインデックスを返す + return i # 基準値のインデックスを返す def quick_sort(self, nums: list[int], left: int, right: int): """クイックソート""" - # サブ配列の長さが1のときに再帰を終了 + # 部分配列の長さが 1 なら再帰を終了する if left >= right: return - # 分割 + # 番兵分割 pivot = self.partition(nums, left, right) - # 左サブ配列と右サブ配列を再帰的に処理 + # 左右の部分配列を再帰処理 self.quick_sort(nums, left, pivot - 1) self.quick_sort(nums, pivot + 1, right) class QuickSortTailCall: - """クイックソートクラス(末尾再帰最適化)""" + """クイックソートクラス(再帰深度最適化)""" def partition(self, nums: list[int], left: int, right: int) -> int: - """分割""" - # nums[left] をピボットとして使用 + """番兵分割""" + # nums[left] を基準値とする i, j = left, right while i < j: while i < j and nums[j] >= nums[left]: - j -= 1 # 右から左へピボットより小さい最初の要素を探す + j -= 1 # 右から左へ基準値未満の最初の要素を探す while i < j and nums[i] <= nums[left]: - i += 1 # 左から右へピボットより大きい最初の要素を探す - # 要素を交換 + i += 1 # 左から右へ基準値より大きい最初の要素を探す + # 要素の交換 nums[i], nums[j] = nums[j], nums[i] - # ピボットを2つのサブ配列の境界に交換 + # 基準値を 2 つの部分配列の境界へ交換する nums[i], nums[left] = nums[left], nums[i] - return i # ピボットのインデックスを返す + return i # 基準値のインデックスを返す def quick_sort(self, nums: list[int], left: int, right: int): - """クイックソート(末尾再帰最適化)""" - # サブ配列の長さが1のときに終了 + """クイックソート(再帰深度最適化)""" + # 部分配列の長さが 1 なら終了 while left < right: - # 分割操作 + # 番兵による分割処理 pivot = self.partition(nums, left, right) - # 2つのサブ配列のうち短い方に対してクイックソートを実行 + # 2 つの部分配列のうち短いほうにクイックソートを適用する if pivot - left < right - pivot: - self.quick_sort(nums, left, pivot - 1) # 左サブ配列を再帰的にソート - left = pivot + 1 # 残りの未ソート区間は [pivot + 1, right] + self.quick_sort(nums, left, pivot - 1) # 左部分配列を再帰的にソート + left = pivot + 1 # 未ソート区間の残りは [pivot + 1, right] else: - self.quick_sort(nums, pivot + 1, right) # 右サブ配列を再帰的にソート - right = pivot - 1 # 残りの未ソート区間は [left, pivot - 1] + self.quick_sort(nums, pivot + 1, right) # 右部分配列を再帰的にソート + right = pivot - 1 # 未ソート区間の残りは [left, pivot - 1] -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": # クイックソート nums = [2, 4, 1, 0, 3, 5] QuickSort().quick_sort(nums, 0, len(nums) - 1) - print("クイックソート完了 nums =", nums) + print("クイックソート完了後 nums =", nums) - # クイックソート(中央値ピボット最適化) + # クイックソート(中央値の基準値で最適化) nums1 = [2, 4, 1, 0, 3, 5] QuickSortMedian().quick_sort(nums1, 0, len(nums1) - 1) - print("クイックソート(中央値ピボット最適化)完了 nums =", nums1) + print("クイックソート(中央値ピボット最適化)完了後 nums =", nums1) - # クイックソート(末尾再帰最適化) + # クイックソート(再帰深度最適化) nums2 = [2, 4, 1, 0, 3, 5] QuickSortTailCall().quick_sort(nums2, 0, len(nums2) - 1) - print("クイックソート(末尾再帰最適化)完了 nums =", nums2) \ No newline at end of file + print("クイックソート(再帰深度最適化)完了後 nums =", nums2) diff --git a/ja/codes/python/chapter_sorting/radix_sort.py b/ja/codes/python/chapter_sorting/radix_sort.py index ca8982da0..fa29db71a 100644 --- a/ja/codes/python/chapter_sorting/radix_sort.py +++ b/ja/codes/python/chapter_sorting/radix_sort.py @@ -6,51 +6,51 @@ Author: krahets (krahets@163.com) def digit(num: int, exp: int) -> int: - """要素 num の k 番目の桁を取得、exp = 10^(k-1)""" - # k の代わりに exp を渡すことで、ここでコストの高い累乗計算を避けることができる + """要素 num の下から k 桁目を取得(exp = 10^(k-1))""" + # ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す return (num // exp) % 10 def counting_sort_digit(nums: list[int], exp: int): - """計数ソート(nums の k 番目の桁に基づく)""" - # 10進数の桁の範囲は 0~9、したがって長さ10のバケット配列が必要 + """計数ソート(nums の k 桁目でソート)""" + # 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 counter = [0] * 10 n = len(nums) - # 数字 0~9 の出現回数を統計 + # 0~9 の各数字の出現回数を集計する for i in range(n): - d = digit(nums[i], exp) # nums[i] の k 番目の桁を取得、d とする - counter[d] += 1 # 数字 d の出現回数を統計 - # 前置和を計算し、「出現回数」を「配列インデックス」に変換 + d = digit(nums[i], exp) # nums[i] の第 k 位を取得し、d とする + counter[d] += 1 # 数字 d の出現回数を数える + # 累積和を求め、「出現回数」を「配列インデックス」に変換する for i in range(1, 10): counter[i] += counter[i - 1] - # 逆順に走査し、バケット統計に基づいて各要素を res に配置 + # 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する res = [0] * n for i in range(n - 1, -1, -1): d = digit(nums[i], exp) - j = counter[d] - 1 # 配列内の d のインデックス j を取得 - res[j] = nums[i] # 現在の要素をインデックス j に配置 - counter[d] -= 1 # d の数を1減らす - # 結果を使用して元の配列 nums を上書き + j = counter[d] - 1 # d の配列内インデックス j を取得する + res[j] = nums[i] # 現在の要素をインデックス j に格納する + counter[d] -= 1 # d の個数を 1 減らす + # 結果で元の配列 nums を上書きする for i in range(n): nums[i] = res[i] def radix_sort(nums: list[int]): """基数ソート""" - # 配列の最大要素を取得し、最大桁数を判定するために使用 + # 最大桁数の判定用に配列の最大要素を取得 m = max(nums) - # 最下位桁から最上位桁まで走査 + # 下位桁から上位桁の順に走査する exp = 1 while exp <= m: - # 配列要素の k 番目の桁に対して計数ソートを実行 + # 配列要素の k 桁目に対して計数ソートを行う # k = 1 -> exp = 1 # k = 2 -> exp = 10 - # つまり、exp = 10^(k-1) + # つまり exp = 10^(k-1) counting_sort_digit(nums, exp) exp *= 10 -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": # 基数ソート nums = [ @@ -66,4 +66,4 @@ if __name__ == "__main__": 63832996, ] radix_sort(nums) - print("基数ソート完了 nums =", nums) \ No newline at end of file + print("基数ソート完了後 nums =", nums) diff --git a/ja/codes/python/chapter_sorting/selection_sort.py b/ja/codes/python/chapter_sorting/selection_sort.py index fff78ce71..00793b606 100644 --- a/ja/codes/python/chapter_sorting/selection_sort.py +++ b/ja/codes/python/chapter_sorting/selection_sort.py @@ -8,19 +8,19 @@ Author: krahets (krahets@163.com) def selection_sort(nums: list[int]): """選択ソート""" n = len(nums) - # 外側のループ:未ソート範囲は [i, n-1] + # 外側ループ:未整列区間は [i, n-1] for i in range(n - 1): - # 内側のループ:未ソート範囲内で最小要素を見つける + # 内側のループ:未ソート区間の最小要素を見つける k = i for j in range(i + 1, n): if nums[j] < nums[k]: k = j # 最小要素のインデックスを記録 - # 最小要素を未ソート範囲の先頭要素と交換 + # その最小要素を未整列区間の先頭要素と交換する nums[i], nums[k] = nums[k], nums[i] -"""ドライバーコード""" +"""Driver Code""" if __name__ == "__main__": nums = [4, 1, 3, 1, 5, 2] selection_sort(nums) - print("選択ソート完了 nums =", nums) \ No newline at end of file + print("選択ソート完了後 nums =", nums) diff --git a/ja/codes/python/chapter_stack_and_queue/array_deque.py b/ja/codes/python/chapter_stack_and_queue/array_deque.py index a43f42f71..d6f0164e1 100644 --- a/ja/codes/python/chapter_stack_and_queue/array_deque.py +++ b/ja/codes/python/chapter_stack_and_queue/array_deque.py @@ -6,7 +6,7 @@ Author: krahets (krahets@163.com) class ArrayDeque: - """循環配列ベースの双端キュークラス""" + """循環配列ベースの両端キュー""" def __init__(self, capacity: int): """コンストラクタ""" @@ -15,78 +15,78 @@ class ArrayDeque: self._size: int = 0 def capacity(self) -> int: - """双端キューの容量を取得""" + """両端キューの容量を取得""" return len(self._nums) def size(self) -> int: - """双端キューの長さを取得""" + """両端キューの長さを取得""" return self._size def is_empty(self) -> bool: - """双端キューが空かどうかを判定""" + """両端キューが空かどうかを判定""" return self._size == 0 def index(self, i: int) -> int: """循環配列のインデックスを計算""" - # モジュロ演算によって循環配列を実装 - # i が配列の末尾を超えた場合、先頭に戻る - # i が配列の先頭を超えた場合、末尾に戻る + # 剰余演算により配列の先頭と末尾をつなげる + # i が配列の末尾を越えたら先頭に戻る + # i が配列の先頭を越えて前に出たら末尾に戻る return (i + self.capacity()) % self.capacity() def push_first(self, num: int): - """前端エンキュー""" + """キュー先頭にエンキュー""" if self._size == self.capacity(): - print("双端キューが満杯です") + print("両端キューがいっぱいです") return - # フロントポインタを左に1つ移動 - # モジュロ演算によってフロントが配列の先頭を超えて末尾に戻ることを実装 + # 先頭ポインタを左に 1 つ移動する + # 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする self._front = self.index(self._front - 1) - # num を前端に追加 + # num をキュー先頭に追加 self._nums[self._front] = num self._size += 1 def push_last(self, num: int): - """後端エンキュー""" + """キュー末尾にエンキュー""" if self._size == self.capacity(): - print("双端キューが満杯です") + print("両端キューがいっぱいです") return - # リアポインタを計算、リアインデックス + 1 を指す + # キュー末尾ポインタを計算し、末尾インデックス + 1 を指す rear = self.index(self._front + self._size) - # num を後端に追加 + # num をキュー末尾に追加 self._nums[rear] = num self._size += 1 def pop_first(self) -> int: - """前端デキュー""" + """キュー先頭からデキュー""" num = self.peek_first() - # フロントポインタを1つ後ろに移動 + # 先頭ポインタを 1 つ後ろへ進める self._front = self.index(self._front + 1) self._size -= 1 return num def pop_last(self) -> int: - """後端デキュー""" + """キュー末尾からデキュー""" num = self.peek_last() self._size -= 1 return num def peek_first(self) -> int: - """前端要素にアクセス""" + """キュー先頭の要素にアクセス""" if self.is_empty(): - raise IndexError("Double-ended queue is empty") + raise IndexError("両端キューが空です") return self._nums[self._front] def peek_last(self) -> int: - """後端要素にアクセス""" + """キュー末尾の要素にアクセス""" if self.is_empty(): - raise IndexError("Double-ended queue is empty") - # 後端要素のインデックスを計算 + raise IndexError("両端キューが空です") + # 末尾要素のインデックスを計算 last = self.index(self._front + self._size - 1) return self._nums[last] def to_array(self) -> list[int]: """出力用の配列を返す""" - # 有効な長さ範囲内の要素のみを変換 + # 有効長の範囲内のリスト要素のみを変換 res = [] for i in range(self._size): res.append(self._nums[self.index(self._front + i)]) @@ -95,35 +95,35 @@ class ArrayDeque: """Driver Code""" if __name__ == "__main__": - # 双端キューを初期化 + # 両端キューを初期化 deque = ArrayDeque(10) deque.push_last(3) deque.push_last(2) deque.push_last(5) - print("双端キュー deque =", deque.to_array()) + print("両端キュー deque =", deque.to_array()) # 要素にアクセス peek_first: int = deque.peek_first() - print("前端要素 peek_first =", peek_first) + print("先頭要素 peek_first =", peek_first) peek_last: int = deque.peek_last() - print("後端要素 peek_last =", peek_last) + print("末尾要素 peek_last =", peek_last) # 要素をエンキュー deque.push_last(4) - print("要素 4 を後端エンキュー、deque =", deque.to_array()) + print("要素 4 を末尾に追加した後 deque =", deque.to_array()) deque.push_first(1) - print("要素 1 を前端エンキュー、deque =", deque.to_array()) + print("要素 1 を先頭に追加した後 deque =", deque.to_array()) # 要素をデキュー pop_last: int = deque.pop_last() - print("後端でデキューされた要素 =", pop_last, "、後端デキュー後の deque =", deque.to_array()) + print("末尾から取り出した要素 =", pop_last, "、末尾から取り出した後 deque =", deque.to_array()) pop_first: int = deque.pop_first() - print("前端でデキューされた要素 =", pop_first, "、前端デキュー後の deque =", deque.to_array()) + print("先頭から取り出した要素 =", pop_first, "、先頭から取り出した後 deque =", deque.to_array()) - # 双端キューの長さを取得 + # 両端キューの長さを取得 size: int = deque.size() - print("双端キューの長さ size =", size) + print("両端キューの長さ size =", size) - # 双端キューが空かどうかを判定 + # 両端キューが空かどうかを判定 is_empty: bool = deque.is_empty() - print("双端キューが空かどうか =", is_empty) \ No newline at end of file + print("両端キューが空かどうか =", is_empty) diff --git a/ja/codes/python/chapter_stack_and_queue/array_queue.py b/ja/codes/python/chapter_stack_and_queue/array_queue.py index dc61d4122..ace8861b8 100644 --- a/ja/codes/python/chapter_stack_and_queue/array_queue.py +++ b/ja/codes/python/chapter_stack_and_queue/array_queue.py @@ -6,12 +6,12 @@ Author: Peng Chen (pengchzn@gmail.com) class ArrayQueue: - """循環配列ベースのキュークラス""" + """循環配列ベースのキュー""" def __init__(self, size: int): """コンストラクタ""" self._nums: list[int] = [0] * size # キュー要素を格納する配列 - self._front: int = 0 # フロントポインタ、フロント要素を指す + self._front: int = 0 # 先頭ポインタ。先頭要素を指す self._size: int = 0 # キューの長さ def capacity(self) -> int: @@ -29,30 +29,30 @@ class ArrayQueue: def push(self, num: int): """エンキュー""" if self._size == self.capacity(): - raise IndexError("Queue is full") - # リアポインタを計算、リアインデックス + 1 を指す - # モジュロ演算を使用してリアポインタを配列の末尾から先頭に戻す + raise IndexError("キューがいっぱいです") + # 末尾ポインタを計算し、末尾インデックス + 1 を指す + # 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする rear: int = (self._front + self._size) % self.capacity() - # num をリアに追加 + # num をキュー末尾に追加 self._nums[rear] = num self._size += 1 def pop(self) -> int: """デキュー""" num: int = self.peek() - # フロントポインタを1つ後ろに移動、末尾を超えた場合は配列の先頭に戻る + # 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す self._front = (self._front + 1) % self.capacity() self._size -= 1 return num def peek(self) -> int: - """フロント要素にアクセス""" + """キュー先頭の要素にアクセス""" if self.is_empty(): - raise IndexError("Queue is empty") + raise IndexError("キューが空です") return self._nums[self._front] def to_list(self) -> list[int]: - """出力用の配列を返す""" + """表示用のリストを返す""" res = [0] * self.size() j: int = self._front for i in range(self.size()): @@ -74,14 +74,14 @@ if __name__ == "__main__": queue.push(4) print("キュー queue =", queue.to_list()) - # フロント要素にアクセス + # キュー先頭の要素にアクセス peek: int = queue.peek() - print("フロント要素 peek =", peek) + print("先頭要素 peek =", peek) # 要素をデキュー pop: int = queue.pop() - print("デキューされた要素 pop =", pop) - print("デキュー後のキュー =", queue.to_list()) + print("取り出した要素 pop =", pop) + print("取り出した後 queue =", queue.to_list()) # キューの長さを取得 size: int = queue.size() @@ -91,8 +91,8 @@ if __name__ == "__main__": is_empty: bool = queue.is_empty() print("キューが空かどうか =", is_empty) - # 循環配列のテスト + # 循環配列をテストする for i in range(10): queue.push(i) queue.pop() - print("第", i, "回目のエンキュー + デキューで、queue =", queue.to_list()) \ No newline at end of file + print("第", i, "回目の追加 + 取り出し後 queue = ", queue.to_list()) diff --git a/ja/codes/python/chapter_stack_and_queue/array_stack.py b/ja/codes/python/chapter_stack_and_queue/array_stack.py index 9d5e5936f..ca6daa396 100644 --- a/ja/codes/python/chapter_stack_and_queue/array_stack.py +++ b/ja/codes/python/chapter_stack_and_queue/array_stack.py @@ -6,7 +6,7 @@ Author: Peng Chen (pengchzn@gmail.com) class ArrayStack: - """配列ベースのスタッククラス""" + """配列ベースのスタック""" def __init__(self): """コンストラクタ""" @@ -27,17 +27,17 @@ class ArrayStack: def pop(self) -> int: """ポップ""" if self.is_empty(): - raise IndexError("Stack is empty") + raise IndexError("スタックが空です") return self._stack.pop() def peek(self) -> int: - """スタックトップ要素にアクセス""" + """スタックトップの要素にアクセス""" if self.is_empty(): - raise IndexError("Stack is empty") + raise IndexError("スタックが空です") return self._stack[-1] def to_list(self) -> list[int]: - """出力用の配列を返す""" + """表示用のリストを返す""" return self._stack @@ -54,14 +54,14 @@ if __name__ == "__main__": stack.push(4) print("スタック stack =", stack.to_list()) - # スタックトップ要素にアクセス + # スタックトップの要素にアクセス peek: int = stack.peek() print("スタックトップ要素 peek =", peek) # 要素をポップ pop: int = stack.pop() - print("ポップされた要素 pop =", pop) - print("ポップ後のスタック =", stack.to_list()) + print("ポップした要素 pop =", pop) + print("ポップ後 stack =", stack.to_list()) # スタックの長さを取得 size: int = stack.size() @@ -69,4 +69,4 @@ if __name__ == "__main__": # 空かどうかを判定 is_empty: bool = stack.is_empty() - print("スタックが空かどうか =", is_empty) \ No newline at end of file + print("スタックが空かどうか =", is_empty) diff --git a/ja/codes/python/chapter_stack_and_queue/deque.py b/ja/codes/python/chapter_stack_and_queue/deque.py index d72a1df1c..4ef75f804 100644 --- a/ja/codes/python/chapter_stack_and_queue/deque.py +++ b/ja/codes/python/chapter_stack_and_queue/deque.py @@ -8,35 +8,35 @@ from collections import deque """Driver Code""" if __name__ == "__main__": - # 双端キューを初期化 + # 両端キューを初期化 deq: deque[int] = deque() # 要素をエンキュー - deq.append(2) # 後端に追加 + deq.append(2) # 末尾に追加する deq.append(5) deq.append(4) - deq.appendleft(3) # 前端に追加 + deq.appendleft(3) # 先頭に追加する deq.appendleft(1) - print("双端キュー deque =", deq) + print("両端キュー deque =", deq) # 要素にアクセス - front: int = deq[0] # 前端要素 - print("前端要素 front =", front) - rear: int = deq[-1] # 後端要素 - print("後端要素 rear =", rear) + front: int = deq[0] # 先頭要素 + print("先頭要素 front =", front) + rear: int = deq[-1] # 末尾要素 + print("末尾要素 rear =", rear) # 要素をデキュー - pop_front: int = deq.popleft() # 前端要素のデキュー - print("前端でデキューされた要素 pop_front =", pop_front) - print("前端デキュー後のデック =", deq) - pop_rear: int = deq.pop() # 後端要素のデキュー - print("後端でデキューされた要素 pop_rear =", pop_rear) - print("後端デキュー後のデック =", deq) + pop_front: int = deq.popleft() # 先頭要素を取り出す + print("先頭から取り出した要素 pop_front =", pop_front) + print("先頭から取り出した後 deque =", deq) + pop_rear: int = deq.pop() # 末尾要素を取り出す + print("末尾から取り出した要素 pop_rear =", pop_rear) + print("末尾から取り出した後 deque =", deq) - # 双端キューの長さを取得 + # 両端キューの長さを取得 size: int = len(deq) - print("双端キューの長さ size =", size) + print("両端キューの長さ size =", size) - # 双端キューが空かどうかを判定 + # 両端キューが空かどうかを判定 is_empty: bool = len(deq) == 0 - print("双端キューが空かどうか =", is_empty) \ No newline at end of file + print("両端キューが空かどうか =", is_empty) diff --git a/ja/codes/python/chapter_stack_and_queue/linkedlist_deque.py b/ja/codes/python/chapter_stack_and_queue/linkedlist_deque.py index f4ea6b967..1eea9c44d 100644 --- a/ja/codes/python/chapter_stack_and_queue/linkedlist_deque.py +++ b/ja/codes/python/chapter_stack_and_queue/linkedlist_deque.py @@ -16,93 +16,93 @@ class ListNode: class LinkedListDeque: - """双方向連結リストベースの双端キュークラス""" + """双方向連結リストベースの両端キュー""" def __init__(self): """コンストラクタ""" - self._front: ListNode | None = None # ヘッドノード front - self._rear: ListNode | None = None # テールノード rear - self._size: int = 0 # 双端キューの長さ + self._front: ListNode | None = None # 先頭ノード front + self._rear: ListNode | None = None # 末尾ノード rear + self._size: int = 0 # 両端キューの長さ def size(self) -> int: - """双端キューの長さを取得""" + """両端キューの長さを取得""" return self._size def is_empty(self) -> bool: - """双端キューが空かどうかを判定""" + """両端キューが空かどうかを判定""" return self._size == 0 def push(self, num: int, is_front: bool): """エンキュー操作""" node = ListNode(num) - # リストが空の場合、front と rear の両方を node に向ける + # 連結リストが空なら、front と rear の両方を node に向ける if self.is_empty(): self._front = self._rear = node - # 前端エンキュー操作 + # 先頭へのエンキュー操作 elif is_front: - # ノードをリストの先頭に追加 + # node を連結リストの先頭に追加 self._front.prev = node node.next = self._front - self._front = node # ヘッドノードを更新 - # 後端エンキュー操作 + self._front = node # 先頭ノードを更新する + # 末尾へのエンキュー操作 else: - # ノードをリストの末尾に追加 + # node を連結リストの末尾に追加 self._rear.next = node node.prev = self._rear - self._rear = node # テールノードを更新 + self._rear = node # 末尾ノードを更新する self._size += 1 # キューの長さを更新 def push_first(self, num: int): - """前端エンキュー""" + """キュー先頭にエンキュー""" self.push(num, True) def push_last(self, num: int): - """後端エンキュー""" + """キュー末尾にエンキュー""" self.push(num, False) def pop(self, is_front: bool) -> int: """デキュー操作""" if self.is_empty(): - raise IndexError("Double-ended queue is empty") - # 前端デキュー操作 + raise IndexError("両端キューが空です") + # キュー先頭からの取り出し if is_front: - val: int = self._front.val # ヘッドノードの値を一時的に保存 - # ヘッドノードを削除 + val: int = self._front.val # 先頭ノードの値を一時保存 + # 先頭ノードを削除 fnext: ListNode | None = self._front.next if fnext is not None: fnext.prev = None self._front.next = None - self._front = fnext # ヘッドノードを更新 - # 後端デキュー操作 + self._front = fnext # 先頭ノードを更新する + # キュー末尾からの取り出し else: - val: int = self._rear.val # テールノードの値を一時的に保存 - # テールノードを削除 + val: int = self._rear.val # 末尾ノードの値を一時保存 + # 末尾ノードを削除 rprev: ListNode | None = self._rear.prev if rprev is not None: rprev.next = None self._rear.prev = None - self._rear = rprev # テールノードを更新 + self._rear = rprev # 末尾ノードを更新する self._size -= 1 # キューの長さを更新 return val def pop_first(self) -> int: - """前端デキュー""" + """キュー先頭からデキュー""" return self.pop(True) def pop_last(self) -> int: - """後端デキュー""" + """キュー末尾からデキュー""" return self.pop(False) def peek_first(self) -> int: - """前端要素にアクセス""" + """キュー先頭の要素にアクセス""" if self.is_empty(): - raise IndexError("Double-ended queue is empty") + raise IndexError("両端キューが空です") return self._front.val def peek_last(self) -> int: - """後端要素にアクセス""" + """キュー末尾の要素にアクセス""" if self.is_empty(): - raise IndexError("Double-ended queue is empty") + raise IndexError("両端キューが空です") return self._rear.val def to_array(self) -> list[int]: @@ -117,35 +117,35 @@ class LinkedListDeque: """Driver Code""" if __name__ == "__main__": - # 双端キューを初期化 + # 両端キューを初期化 deque = LinkedListDeque() deque.push_last(3) deque.push_last(2) deque.push_last(5) - print("双端キュー deque =", deque.to_array()) + print("両端キュー deque =", deque.to_array()) # 要素にアクセス peek_first: int = deque.peek_first() - print("前端要素 peek_first =", peek_first) + print("先頭要素 peek_first =", peek_first) peek_last: int = deque.peek_last() - print("後端要素 peek_last =", peek_last) + print("末尾要素 peek_last =", peek_last) # 要素をエンキュー deque.push_last(4) - print("要素 4 を後端エンキュー、deque =", deque.to_array()) + print("要素 4 を末尾に追加した後 deque =", deque.to_array()) deque.push_first(1) - print("要素 1 を前端エンキュー、deque =", deque.to_array()) + print("要素 1 を先頭に追加した後 deque =", deque.to_array()) # 要素をデキュー pop_last: int = deque.pop_last() - print("後端でデキューされた要素 =", pop_last, "、後端デキュー後の deque =", deque.to_array()) + print("末尾から取り出した要素 =", pop_last, "、末尾から取り出した後 deque =", deque.to_array()) pop_first: int = deque.pop_first() - print("前端でデキューされた要素 =", pop_first, "、前端デキュー後の deque =", deque.to_array()) + print("先頭から取り出した要素 =", pop_first, "、先頭から取り出した後 deque =", deque.to_array()) - # 双端キューの長さを取得 + # 両端キューの長さを取得 size: int = deque.size() - print("双端キューの長さ size =", size) + print("両端キューの長さ size =", size) - # 双端キューが空かどうかを判定 + # 両端キューが空かどうかを判定 is_empty: bool = deque.is_empty() - print("双端キューが空かどうか =", is_empty) \ No newline at end of file + print("両端キューが空かどうか =", is_empty) diff --git a/ja/codes/python/chapter_stack_and_queue/linkedlist_queue.py b/ja/codes/python/chapter_stack_and_queue/linkedlist_queue.py index b4132240a..1f66f5a93 100644 --- a/ja/codes/python/chapter_stack_and_queue/linkedlist_queue.py +++ b/ja/codes/python/chapter_stack_and_queue/linkedlist_queue.py @@ -12,12 +12,12 @@ from modules import ListNode class LinkedListQueue: - """連結リストベースのキュークラス""" + """連結リストベースのキュー""" def __init__(self): """コンストラクタ""" - self._front: ListNode | None = None # ヘッドノード front - self._rear: ListNode | None = None # テールノード rear + self._front: ListNode | None = None # 先頭ノード front + self._rear: ListNode | None = None # 末尾ノード rear self._size: int = 0 def size(self) -> int: @@ -30,13 +30,13 @@ class LinkedListQueue: def push(self, num: int): """エンキュー""" - # テールノードの後ろに num を追加 + # 末尾ノードの後ろに num を追加 node = ListNode(num) - # キューが空の場合、ヘッドとテールノードの両方をそのノードに向ける + # キューが空なら、先頭・末尾ノードをともにそのノードに設定 if self._front is None: self._front = node self._rear = node - # キューが空でない場合、そのノードをテールノードの後ろに追加 + # キューが空でなければ、そのノードを末尾ノードの後ろに追加 else: self._rear.next = node self._rear = node @@ -45,19 +45,19 @@ class LinkedListQueue: def pop(self) -> int: """デキュー""" num = self.peek() - # ヘッドノードを削除 + # 先頭ノードを削除 self._front = self._front.next self._size -= 1 return num def peek(self) -> int: - """フロント要素にアクセス""" + """キュー先頭の要素にアクセス""" if self.is_empty(): - raise IndexError("Queue is empty") + raise IndexError("キューが空です") return self._front.val def to_list(self) -> list[int]: - """出力用のリストに変換""" + """表示用にリストへ変換""" queue = [] temp = self._front while temp: @@ -79,14 +79,14 @@ if __name__ == "__main__": queue.push(4) print("キュー queue =", queue.to_list()) - # フロント要素にアクセス + # キュー先頭の要素にアクセス peek: int = queue.peek() - print("フロント要素 front =", peek) + print("先頭要素 front =", peek) # 要素をデキュー pop_front: int = queue.pop() - print("デキューされた要素 pop =", pop_front) - print("デキュー後のキュー =", queue.to_list()) + print("取り出した要素 pop =", pop_front) + print("取り出した後 queue =", queue.to_list()) # キューの長さを取得 size: int = queue.size() @@ -94,4 +94,4 @@ if __name__ == "__main__": # キューが空かどうかを判定 is_empty: bool = queue.is_empty() - print("キューが空かどうか =", is_empty) \ No newline at end of file + print("キューが空かどうか =", is_empty) diff --git a/ja/codes/python/chapter_stack_and_queue/linkedlist_stack.py b/ja/codes/python/chapter_stack_and_queue/linkedlist_stack.py index dd5a54ba0..e37e9b84f 100644 --- a/ja/codes/python/chapter_stack_and_queue/linkedlist_stack.py +++ b/ja/codes/python/chapter_stack_and_queue/linkedlist_stack.py @@ -12,7 +12,7 @@ from modules import ListNode class LinkedListStack: - """連結リストベースのスタッククラス""" + """連結リストベースのスタック""" def __init__(self): """コンストラクタ""" @@ -42,13 +42,13 @@ class LinkedListStack: return num def peek(self) -> int: - """スタックトップ要素にアクセス""" + """スタックトップの要素にアクセス""" if self.is_empty(): - raise IndexError("Stack is empty") + raise IndexError("スタックが空です") return self._peek.val def to_list(self) -> list[int]: - """出力用のリストに変換""" + """表示用にリストへ変換""" arr = [] node = self._peek while node: @@ -71,14 +71,14 @@ if __name__ == "__main__": stack.push(4) print("スタック stack =", stack.to_list()) - # スタックトップ要素にアクセス + # スタックトップの要素にアクセス peek: int = stack.peek() print("スタックトップ要素 peek =", peek) # 要素をポップ pop: int = stack.pop() - print("ポップされた要素 pop =", pop) - print("ポップ後のスタック =", stack.to_list()) + print("ポップした要素 pop =", pop) + print("ポップ後 stack =", stack.to_list()) # スタックの長さを取得 size: int = stack.size() @@ -86,4 +86,4 @@ if __name__ == "__main__": # 空かどうかを判定 is_empty: bool = stack.is_empty() - print("スタックが空かどうか =", is_empty) \ No newline at end of file + print("スタックが空かどうか =", is_empty) diff --git a/ja/codes/python/chapter_stack_and_queue/queue.py b/ja/codes/python/chapter_stack_and_queue/queue.py index 267c0cb61..826214101 100644 --- a/ja/codes/python/chapter_stack_and_queue/queue.py +++ b/ja/codes/python/chapter_stack_and_queue/queue.py @@ -8,9 +8,9 @@ from collections import deque """Driver Code""" if __name__ == "__main__": - # キューを初期化 - # Pythonでは、一般的にdequeクラスをキューとして考えます - # queue.Queue()は純粋なキュークラスですが、あまりユーザーフレンドリーではありません + # キューを初期化する + # Python では通常、両端キュー deque をキューとして使う + # queue.Queue() は正統なキュークラスだが、あまり使いやすくない que: deque[int] = deque() # 要素をエンキュー @@ -21,14 +21,14 @@ if __name__ == "__main__": que.append(4) print("キュー que =", que) - # フロント要素にアクセス + # キュー先頭の要素にアクセス front: int = que[0] - print("フロント要素 front =", front) + print("先頭要素 front =", front) # 要素をデキュー pop: int = que.popleft() - print("デキューされた要素 pop =", pop) - print("デキュー後のキュー =", que) + print("取り出した要素 pop =", pop) + print("取り出し後 que =", que) # キューの長さを取得 size: int = len(que) @@ -36,4 +36,4 @@ if __name__ == "__main__": # キューが空かどうかを判定 is_empty: bool = len(que) == 0 - print("キューが空かどうか =", is_empty) \ No newline at end of file + print("キューが空かどうか =", is_empty) diff --git a/ja/codes/python/chapter_stack_and_queue/stack.py b/ja/codes/python/chapter_stack_and_queue/stack.py index 80e659331..b0d28deb6 100644 --- a/ja/codes/python/chapter_stack_and_queue/stack.py +++ b/ja/codes/python/chapter_stack_and_queue/stack.py @@ -6,8 +6,8 @@ Author: Peng Chen (pengchzn@gmail.com) """Driver Code""" if __name__ == "__main__": - # スタックを初期化 - # Pythonには組み込みのスタッククラスはありませんが、リストをスタックとして使用できます + # スタックを初期化する + # Python には組み込みのスタッククラスがないため、list をスタックとして使える stack: list[int] = [] # 要素をプッシュ @@ -18,14 +18,14 @@ if __name__ == "__main__": stack.append(4) print("スタック stack =", stack) - # スタックトップ要素にアクセス + # スタックトップの要素にアクセス peek: int = stack[-1] print("スタックトップ要素 peek =", peek) # 要素をポップ pop: int = stack.pop() - print("ポップされた要素 pop =", pop) - print("ポップ後のスタック =", stack) + print("ポップした要素 pop =", pop) + print("ポップ後 stack =", stack) # スタックの長さを取得 size: int = len(stack) @@ -33,4 +33,4 @@ if __name__ == "__main__": # 空かどうかを判定 is_empty: bool = len(stack) == 0 - print("スタックが空かどうか =", is_empty) \ No newline at end of file + print("スタックが空かどうか =", is_empty) diff --git a/ja/codes/python/chapter_tree/array_binary_tree.py b/ja/codes/python/chapter_tree/array_binary_tree.py index 2e42b6df0..b1f5b54ab 100644 --- a/ja/codes/python/chapter_tree/array_binary_tree.py +++ b/ja/codes/python/chapter_tree/array_binary_tree.py @@ -12,49 +12,49 @@ from modules import TreeNode, list_to_tree, print_tree class ArrayBinaryTree: - """配列ベースの二分木クラス""" + """配列表現による二分木クラス""" def __init__(self, arr: list[int | None]): """コンストラクタ""" self._tree = list(arr) def size(self): - """リストの容量""" + """リスト容量""" return len(self._tree) def val(self, i: int) -> int | None: - """インデックスiのノードの値を取得""" - # インデックスが範囲外の場合、Noneを返し、空席を表す + """インデックス i のノードの値を取得""" + # インデックスが範囲外なら、空きを表す None を返す if i < 0 or i >= self.size(): return None return self._tree[i] def left(self, i: int) -> int | None: - """インデックスiのノードの左の子のインデックスを取得""" + """インデックス i のノードの左子ノードのインデックスを取得""" return 2 * i + 1 def right(self, i: int) -> int | None: - """インデックスiのノードの右の子のインデックスを取得""" + """インデックス i のノードの右子ノードのインデックスを取得""" return 2 * i + 2 def parent(self, i: int) -> int | None: - """インデックスiのノードの親のインデックスを取得""" + """インデックス i のノードの親ノードのインデックスを取得""" return (i - 1) // 2 def level_order(self) -> list[int]: """レベル順走査""" self.res = [] - # 配列を走査 + # 配列を直接走査する for i in range(self.size()): if self.val(i) is not None: self.res.append(self.val(i)) return self.res def dfs(self, i: int, order: str): - """深さ優先走査""" + """深さ優先探索""" if self.val(i) is None: return - # 前順走査 + # 先行順走査 if order == "pre": self.res.append(self.val(i)) self.dfs(self.left(i), order) @@ -67,7 +67,7 @@ class ArrayBinaryTree: self.res.append(self.val(i)) def pre_order(self) -> list[int]: - """前順走査""" + """先行順走査""" self.res = [] self.dfs(0, order="pre") return self.res @@ -85,35 +85,35 @@ class ArrayBinaryTree: return self.res -"""ドライバコード""" +"""Driver Code""" if __name__ == "__main__": # 二分木を初期化 - # 特定の関数を使用して配列を二分木に変換 + # ここでは、配列から直接二分木を生成する関数を利用する arr = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] root = list_to_tree(arr) print("\n二分木を初期化\n") - print("二分木の配列表現:") + print("二分木の配列表現:") print(arr) - print("二分木の連結リスト表現:") + print("二分木の連結リスト表現:") print_tree(root) - # 配列ベースの二分木クラス + # 配列表現による二分木クラス abt = ArrayBinaryTree(arr) # ノードにアクセス i = 1 l, r, p = abt.left(i), abt.right(i), abt.parent(i) print(f"\n現在のノードのインデックスは {i}、値は {abt.val(i)}") - print(f"その左の子ノードのインデックスは {l}、値は {abt.val(l)}") - print(f"その右の子ノードのインデックスは {r}、値は {abt.val(r)}") + print(f"その左子ノードのインデックスは {l}、値は {abt.val(l)}") + print(f"その右子ノードのインデックスは {r}、値は {abt.val(r)}") print(f"その親ノードのインデックスは {p}、値は {abt.val(p)}") # 木を走査 res = abt.level_order() - print("\nレベル順走査:", res) + print("\nレベル順走査:", res) res = abt.pre_order() - print("前順走査:", res) + print("先行順走査:", res) res = abt.in_order() - print("中順走査:", res) + print("中間順走査:", res) res = abt.post_order() - print("後順走査:", res) \ No newline at end of file + print("後行順走査:", res) diff --git a/ja/codes/python/chapter_tree/avl_tree.py b/ja/codes/python/chapter_tree/avl_tree.py index f6c62844f..35cc90803 100644 --- a/ja/codes/python/chapter_tree/avl_tree.py +++ b/ja/codes/python/chapter_tree/avl_tree.py @@ -12,67 +12,67 @@ from modules import TreeNode, print_tree class AVLTree: - """AVL木""" + """AVL 木""" def __init__(self): """コンストラクタ""" self._root = None def get_root(self) -> TreeNode | None: - """二分木のルートノードを取得""" + """二分木の根ノードを取得""" return self._root def height(self, node: TreeNode | None) -> int: """ノードの高さを取得""" - # 空ノードの高さは-1、葉ノードの高さは0 + # 空ノードの高さは -1、葉ノードの高さは 0 if node is not None: return node.height return -1 def update_height(self, node: TreeNode | None): - """ノードの高さを更新""" - # ノードの高さ = 最も高い部分木の高さ + 1 + """ノードの高さを更新する""" + # ノードの高さは最も高い部分木の高さ + 1 に等しい node.height = max([self.height(node.left), self.height(node.right)]) + 1 def balance_factor(self, node: TreeNode | None) -> int: - """バランス因子を取得""" - # 空ノードのバランス因子は0 + """平衡係数を取得""" + # 空ノードの平衡係数は 0 if node is None: return 0 - # ノードのバランス因子 = 左部分木の高さ - 右部分木の高さ + # ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ return self.height(node.left) - self.height(node.right) def right_rotate(self, node: TreeNode | None) -> TreeNode | None: - """右回転操作""" + """右回転""" child = node.left grand_child = child.right - # childを中心にnodeを右に回転 + # child を支点として node を右回転させる child.right = node node.left = grand_child - # ノードの高さを更新 + # ノードの高さを更新する self.update_height(node) self.update_height(child) - # 回転後の部分木のルートを返す + # 回転後の部分木の根ノードを返す return child def left_rotate(self, node: TreeNode | None) -> TreeNode | None: - """左回転操作""" + """左回転""" child = node.right grand_child = child.left - # childを中心にnodeを左に回転 + # child を支点として node を左回転させる child.left = node node.right = grand_child - # ノードの高さを更新 + # ノードの高さを更新する self.update_height(node) self.update_height(child) - # 回転後の部分木のルートを返す + # 回転後の部分木の根ノードを返す return child def rotate(self, node: TreeNode | None) -> TreeNode | None: - """回転操作を実行して部分木のバランスを復元""" - # nodeのバランス因子を取得 + """回転操作を行い、この部分木の平衡を回復する""" + # ノード node の平衡係数を取得 balance_factor = self.balance_factor(node) - # 左偏り木 + # 左に偏った木 if balance_factor > 1: if self.balance_factor(node.left) >= 0: # 右回転 @@ -81,7 +81,7 @@ class AVLTree: # 左回転してから右回転 node.left = self.left_rotate(node.left) return self.right_rotate(node) - # 右偏り木 + # 右に偏った木 elif balance_factor < -1: if self.balance_factor(node.right) <= 0: # 左回転 @@ -90,7 +90,7 @@ class AVLTree: # 右回転してから左回転 node.right = self.right_rotate(node.right) return self.left_rotate(node) - # バランスの取れた木、回転不要、戻る + # 平衡木なので回転不要、そのまま返す return node def insert(self, val): @@ -98,20 +98,20 @@ class AVLTree: self._root = self.insert_helper(self._root, val) def insert_helper(self, node: TreeNode | None, val: int) -> TreeNode: - """再帰的にノードを挿入(ヘルパーメソッド)""" + """ノードを再帰的に挿入する(補助メソッド)""" if node is None: return TreeNode(val) - # 1. 挿入位置を見つけてノードを挿入 + # 1. 挿入位置を探索してノードを挿入 if val < node.val: node.left = self.insert_helper(node.left, val) elif val > node.val: node.right = self.insert_helper(node.right, val) else: - # 重複ノードは挿入しない、戻る + # 重複ノードは挿入せず、そのまま返す return node - # ノードの高さを更新 + # ノードの高さを更新する self.update_height(node) - # 2. 回転操作を実行して部分木のバランスを復元 + # 2. 回転操作を行い、部分木の平衡を回復する return self.rotate(node) def remove(self, val: int): @@ -119,10 +119,10 @@ class AVLTree: self._root = self.remove_helper(self._root, val) def remove_helper(self, node: TreeNode | None, val: int) -> TreeNode | None: - """再帰的にノードを削除(ヘルパーメソッド)""" + """ノードを再帰的に削除する(補助メソッド)""" if node is None: return None - # 1. ノードを見つけて削除 + # 1. ノードを探索して削除 if val < node.val: node.left = self.remove_helper(node.left, val) elif val > node.val: @@ -130,71 +130,71 @@ class AVLTree: else: if node.left is None or node.right is None: child = node.left or node.right - # 子ノード数 = 0、ノードを削除して戻る + # 子ノード数 = 0 の場合、node をそのまま削除して返す if child is None: return None - # 子ノード数 = 1、ノードを削除 + # 子ノード数 = 1 の場合、node をそのまま削除する else: node = child else: - # 子ノード数 = 2、中順走査の次のノードを削除し、それで現在のノードを置き換え + # 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える temp = node.right while temp.left is not None: temp = temp.left node.right = self.remove_helper(node.right, temp.val) node.val = temp.val - # ノードの高さを更新 + # ノードの高さを更新する self.update_height(node) - # 2. 回転操作を実行して部分木のバランスを復元 + # 2. 回転操作を行い、部分木の平衡を回復する return self.rotate(node) def search(self, val: int) -> TreeNode | None: """ノードを探索""" cur = self._root - # ループで探索、葉ノードを通過した後にブレーク + # ループで探索し、葉ノードを越えたら抜ける while cur is not None: - # ターゲットノードはcurの右部分木にある + # 目標ノードは cur の右部分木にある if cur.val < val: cur = cur.right - # ターゲットノードはcurの左部分木にある + # 目標ノードは cur の左部分木にある elif cur.val > val: cur = cur.left - # ターゲットノードを発見、ループをブレーク + # 目標ノードが見つかったらループを抜ける else: break - # ターゲットノードを返す + # 目標ノードを返す return cur -"""ドライバコード""" +"""Driver Code""" if __name__ == "__main__": def test_insert(tree: AVLTree, val: int): tree.insert(val) - print("\nノード {} を挿入後、AVL木は".format(val)) + print("\nノード {} を挿入した後、AVL 木は".format(val)) print_tree(tree.get_root()) def test_remove(tree: AVLTree, val: int): tree.remove(val) - print("\nノード {} を削除後、AVL木は".format(val)) + print("\nノード {} を削除した後、AVL 木は".format(val)) print_tree(tree.get_root()) - # 空のAVL木を初期化 + # 空の AVL 木を初期化する avl_tree = AVLTree() - # ノードを挿入 - # AVL木がノード挿入後にバランスを維持する様子に注目 + # ノードを挿入する + # ノード挿入後に AVL 木がどのように平衡を保つかに注目 for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6]: test_insert(avl_tree, val) - # 重複ノードを挿入 + # 重複ノードを挿入する test_insert(avl_tree, 7) - # ノードを削除 - # AVL木がノード削除後にバランスを維持する様子に注目 - test_remove(avl_tree, 8) # 次数0のノードを削除 - test_remove(avl_tree, 5) # 次数1のノードを削除 - test_remove(avl_tree, 4) # 次数2のノードを削除 + # ノードを削除する + # ノード削除後に AVL 木がどのように平衡を保つかに注目 + test_remove(avl_tree, 8) # 次数 0 のノードを削除する + test_remove(avl_tree, 5) # 次数 1 のノードを削除する + test_remove(avl_tree, 4) # 次数 2 のノードを削除する result_node = avl_tree.search(7) - print("\n発見されたノードオブジェクト: {}、ノードの値 = {}".format(result_node, result_node.val)) \ No newline at end of file + print("\n見つかったノードオブジェクトは {}、ノードの値 = {}".format(result_node, result_node.val)) diff --git a/ja/codes/python/chapter_tree/binary_search_tree.py b/ja/codes/python/chapter_tree/binary_search_tree.py index 0a754c17b..267f44f71 100644 --- a/ja/codes/python/chapter_tree/binary_search_tree.py +++ b/ja/codes/python/chapter_tree/binary_search_tree.py @@ -16,46 +16,46 @@ class BinarySearchTree: def __init__(self): """コンストラクタ""" - # 空の木を初期化 + # 空の木を初期化する self._root = None def get_root(self) -> TreeNode | None: - """二分木のルートノードを取得""" + """二分木の根ノードを取得""" return self._root def search(self, num: int) -> TreeNode | None: """ノードを探索""" cur = self._root - # ループで探索、葉ノードを通過した後にブレーク + # ループで探索し、葉ノードを越えたら抜ける while cur is not None: - # ターゲットノードはcurの右部分木にある + # 目標ノードは cur の右部分木にある if cur.val < num: cur = cur.right - # ターゲットノードはcurの左部分木にある + # 目標ノードは cur の左部分木にある elif cur.val > num: cur = cur.left - # ターゲットノードを発見、ループをブレーク + # 目標ノードが見つかったらループを抜ける else: break return cur def insert(self, num: int): """ノードを挿入""" - # 木が空の場合、ルートノードを初期化 + # 木が空なら、根ノードを初期化する if self._root is None: self._root = TreeNode(num) return - # ループで探索、葉ノードを通過した後にブレーク + # ループで探索し、葉ノードを越えたら抜ける cur, pre = self._root, None while cur is not None: - # 重複ノードを発見したため、戻る + # 重複ノードが見つかったら、直ちに返す if cur.val == num: return pre = cur - # 挿入位置はcurの右部分木にある + # 挿入位置は cur の右部分木にある if cur.val < num: cur = cur.right - # 挿入位置はcurの左部分木にある + # 挿入位置は cur の左部分木にある else: cur = cur.left # ノードを挿入 @@ -67,80 +67,80 @@ class BinarySearchTree: def remove(self, num: int): """ノードを削除""" - # 木が空の場合、戻る + # 木が空なら、そのまま早期リターンする if self._root is None: return - # ループで探索、葉ノードを通過した後にブレーク + # ループで探索し、葉ノードを越えたら抜ける cur, pre = self._root, None while cur is not None: - # 削除するノードを発見、ループをブレーク + # 削除対象のノードが見つかったら、ループを抜ける if cur.val == num: break pre = cur - # 削除するノードはcurの右部分木にある + # 削除対象ノードは cur の右部分木にある if cur.val < num: cur = cur.right - # 削除するノードはcurの左部分木にある + # 削除対象ノードは cur の左部分木にある else: cur = cur.left - # 削除するノードが存在しない場合、戻る + # 削除対象ノードがなければそのまま返す if cur is None: return - # 子ノード数 = 0 または 1 + # 子ノード数 = 0 or 1 if cur.left is None or cur.right is None: - # 子ノード数 = 0/1の場合、child = null/その子ノード + # 子ノード数が 0 / 1 のとき、child = null / その子ノード child = cur.left or cur.right - # ノードcurを削除 + # ノード cur を削除する if cur != self._root: if pre.left == cur: pre.left = child else: pre.right = child else: - # 削除されるノードがルートの場合、ルートを再割り当て + # 削除ノードが根ノードなら、根ノードを再設定 self._root = child # 子ノード数 = 2 else: - # curの中順走査の次のノードを取得 + # 中順走査における cur の次ノードを取得 tmp: TreeNode = cur.right while tmp.left is not None: tmp = tmp.left - # 再帰的にノードtmpを削除 + # ノード tmp を再帰的に削除 self.remove(tmp.val) - # curをtmpで置き換え + # tmp で cur を上書きする cur.val = tmp.val -"""ドライバコード""" +"""Driver Code""" if __name__ == "__main__": # 二分探索木を初期化 bst = BinarySearchTree() nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] - # 注意:異なる挿入順序により、様々な木構造が生成される可能性がある。この特定のシーケンスは完全二分木を作成する + # 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる for num in nums: bst.insert(num) - print("\n初期化された二分木は\n") + print("\n初期化した二分木は\n") print_tree(bst.get_root()) # ノードを探索 node = bst.search(7) - print("\n発見されたノードオブジェクト: {}, ノードの値 = {}".format(node, node.val)) + print("\n見つかったノードオブジェクトは: {}、ノードの値 = {}".format(node, node.val)) # ノードを挿入 bst.insert(16) - print("\nノード16を挿入後の二分木は\n") + print("\nノード 16 を挿入した後、二分木は\n") print_tree(bst.get_root()) # ノードを削除 bst.remove(1) - print("\nノード1を削除後の二分木は\n") + print("\nノード 1 を削除した後、二分木は\n") print_tree(bst.get_root()) bst.remove(2) - print("\nノード2を削除後の二分木は\n") + print("\nノード 2 を削除した後、二分木は\n") print_tree(bst.get_root()) bst.remove(4) - print("\nノード4を削除後の二分木は\n") - print_tree(bst.get_root()) \ No newline at end of file + print("\nノード 4 を削除した後、二分木は\n") + print_tree(bst.get_root()) diff --git a/ja/codes/python/chapter_tree/binary_tree.py b/ja/codes/python/chapter_tree/binary_tree.py index fc8eee183..1e55fead9 100644 --- a/ja/codes/python/chapter_tree/binary_tree.py +++ b/ja/codes/python/chapter_tree/binary_tree.py @@ -11,16 +11,16 @@ sys.path.append(str(Path(__file__).parent.parent)) from modules import TreeNode, print_tree -"""ドライバコード""" +"""Driver Code""" if __name__ == "__main__": - # 二分木を初期化 - # ノードを初期化 + # 二分木を初期化する + # ノードを初期化する n1 = TreeNode(val=1) n2 = TreeNode(val=2) n3 = TreeNode(val=3) n4 = TreeNode(val=4) n5 = TreeNode(val=5) - # ノードの参照(ポインタ)を構築 + # ノード間の参照(ポインタ)を構築する n1.left = n2 n1.right = n3 n2.left = n4 @@ -30,12 +30,12 @@ if __name__ == "__main__": # ノードの挿入と削除 P = TreeNode(0) - # ノードPを n1 -> n2 の間に挿入 + # n1 -> n2 の間にノード P を挿入 n1.left = P P.left = n2 - print("\nノードPを挿入後\n") + print("\nノード P を挿入した後\n") print_tree(n1) # ノードを削除 n1.left = n2 - print("\nノードPを削除後\n") - print_tree(n1) \ No newline at end of file + print("\nノード P を削除した後\n") + print_tree(n1) diff --git a/ja/codes/python/chapter_tree/binary_tree_bfs.py b/ja/codes/python/chapter_tree/binary_tree_bfs.py index a329eccb3..516cfe149 100644 --- a/ja/codes/python/chapter_tree/binary_tree_bfs.py +++ b/ja/codes/python/chapter_tree/binary_tree_bfs.py @@ -14,29 +14,29 @@ from collections import deque def level_order(root: TreeNode | None) -> list[int]: """レベル順走査""" - # キューを初期化し、ルートノードを追加 + # キューを初期化し、ルートノードを追加する queue: deque[TreeNode] = deque() queue.append(root) - # 走査シーケンスを格納するリストを初期化 + # 走査順序を保存するためのリストを初期化する res = [] while queue: - node: TreeNode = queue.popleft() # キューからデキュー - res.append(node.val) # ノードの値を保存 + node: TreeNode = queue.popleft() # デキュー + res.append(node.val) # ノードの値を保存する if node.left is not None: - queue.append(node.left) # 左の子ノードをエンキュー + queue.append(node.left) # 左子ノードをキューに追加 if node.right is not None: - queue.append(node.right) # 右の子ノードをエンキュー + queue.append(node.right) # 右子ノードをキューに追加 return res -"""ドライバコード""" +"""Driver Code""" if __name__ == "__main__": # 二分木を初期化 - # 特定の関数を使用して配列を二分木に変換 + # ここでは、配列から直接二分木を生成する関数を利用する root: TreeNode = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) print("\n二分木を初期化\n") print_tree(root) # レベル順走査 res: list[int] = level_order(root) - print("\nレベル順走査のノードシーケンスを出力 = ", res) \ No newline at end of file + print("\nレベル順走査のノード出力シーケンス = ", res) diff --git a/ja/codes/python/chapter_tree/binary_tree_dfs.py b/ja/codes/python/chapter_tree/binary_tree_dfs.py index 6a96335d7..b0ac87b2c 100644 --- a/ja/codes/python/chapter_tree/binary_tree_dfs.py +++ b/ja/codes/python/chapter_tree/binary_tree_dfs.py @@ -12,10 +12,10 @@ from modules import TreeNode, list_to_tree, print_tree def pre_order(root: TreeNode | None): - """前順走査""" + """先行順走査""" if root is None: return - # 訪問順序: ルートノード -> 左部分木 -> 右部分木 + # 訪問順序:根ノード -> 左部分木 -> 右部分木 res.append(root.val) pre_order(root=root.left) pre_order(root=root.right) @@ -25,7 +25,7 @@ def in_order(root: TreeNode | None): """中順走査""" if root is None: return - # 訪問順序: 左部分木 -> ルートノード -> 右部分木 + # 訪問優先順: 左部分木 -> 根ノード -> 右部分木 in_order(root=root.left) res.append(root.val) in_order(root=root.right) @@ -35,31 +35,31 @@ def post_order(root: TreeNode | None): """後順走査""" if root is None: return - # 訪問順序: 左部分木 -> 右部分木 -> ルートノード + # 訪問優先順: 左部分木 -> 右部分木 -> 根ノード post_order(root=root.left) post_order(root=root.right) res.append(root.val) -"""ドライバコード""" +"""Driver Code""" if __name__ == "__main__": # 二分木を初期化 - # 特定の関数を使用して配列を二分木に変換 + # ここでは、配列から直接二分木を生成する関数を利用する root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) print("\n二分木を初期化\n") print_tree(root) - # 前順走査 + # 先行順走査 res = [] pre_order(root) - print("\n前順走査のノードシーケンスを出力 = ", res) + print("\n先行順走査のノード出力シーケンス = ", res) # 中順走査 res.clear() in_order(root) - print("\n中順走査のノードシーケンスを出力 = ", res) + print("\n中間順走査のノード出力シーケンス = ", res) # 後順走査 res.clear() post_order(root) - print("\n後順走査のノードシーケンスを出力 = ", res) \ No newline at end of file + print("\n後行順走査のノード出力シーケンス = ", res) diff --git a/ja/codes/python/modules/__init__.py b/ja/codes/python/modules/__init__.py index 0f1ca57c3..b10799e3c 100644 --- a/ja/codes/python/modules/__init__.py +++ b/ja/codes/python/modules/__init__.py @@ -1,8 +1,8 @@ -# PEP 585に従う - 標準コレクションでの型ヒント +# Follow the PEP 585 - Type Hinting Generics In Standard Collections # https://peps.python.org/pep-0585/ from __future__ import annotations -# 共通ライブラリをここでインポートして、`from module import *`でコードを簡潔にする +# Import common libs here to simplify the code by `from module import *` from .list_node import ( ListNode, list_to_linked_list, @@ -16,4 +16,4 @@ from .print_util import ( print_tree, print_dict, print_heap, -) \ No newline at end of file +) diff --git a/ja/codes/python/modules/list_node.py b/ja/codes/python/modules/list_node.py index b9be878c7..a8fad2f2a 100644 --- a/ja/codes/python/modules/list_node.py +++ b/ja/codes/python/modules/list_node.py @@ -6,15 +6,15 @@ Author: krahets (krahets@163.com) class ListNode: - """連結リストのノードクラス""" + """連結リストノードクラス""" def __init__(self, val: int): - self.val: int = val # ノードの値 + self.val: int = val # ノード値 self.next: ListNode | None = None # 後続ノードへの参照 def list_to_linked_list(arr: list[int]) -> ListNode | None: - """リストを連結リストにデシリアライズ""" + """リストを連結リストにデシリアライズする""" dum = head = ListNode(0) for a in arr: node = ListNode(a) @@ -29,4 +29,4 @@ def linked_list_to_list(head: ListNode | None) -> list[int]: while head: arr.append(head.val) head = head.next - return arr \ No newline at end of file + return arr diff --git a/ja/codes/python/modules/print_util.py b/ja/codes/python/modules/print_util.py index 07bf34d66..fe4b0f3bb 100644 --- a/ja/codes/python/modules/print_util.py +++ b/ja/codes/python/modules/print_util.py @@ -9,7 +9,7 @@ from .list_node import ListNode, linked_list_to_list def print_matrix(mat: list[list[int]]): - """行列を出力""" + """行列を出力する""" s = [] for arr in mat: s.append(" " + str(arr)) @@ -39,10 +39,10 @@ def print_tree( root: TreeNode | None, prev: Trunk | None = None, is_right: bool = False ): """ - 二分木を出力 - この木プリンタはTECHIE DELIGHTから借用 - https://www.techiedelight.com/c-program-print-binary-tree/ - """ +二分木を出力 +This tree printer is borrowed from TECHIE DELIGHT +https://www.techiedelight.com/c-program-print-binary-tree/ +""" if root is None: return @@ -75,7 +75,7 @@ def print_dict(hmap: dict): def print_heap(heap: list[int]): """ヒープを出力""" - print("ヒープの配列表現:", heap) - print("ヒープの木表現:") + print("ヒープの配列表現:", heap) + print("ヒープの木構造表示:") root: TreeNode | None = list_to_tree(heap) - print_tree(root) \ No newline at end of file + print_tree(root) diff --git a/ja/codes/python/modules/tree_node.py b/ja/codes/python/modules/tree_node.py index 7462ab957..1127d7139 100644 --- a/ja/codes/python/modules/tree_node.py +++ b/ja/codes/python/modules/tree_node.py @@ -8,51 +8,51 @@ from collections import deque class TreeNode: - """二分木のノードクラス""" + """二分木ノードクラス""" def __init__(self, val: int = 0): - self.val: int = val # ノードの値 + self.val: int = val # ノード値 self.height: int = 0 # ノードの高さ - self.left: TreeNode | None = None # 左の子ノードへの参照 - self.right: TreeNode | None = None # 右の子ノードへの参照 + self.left: TreeNode | None = None # 左子ノードへの参照 + self.right: TreeNode | None = None # 右子ノードへの参照 - # シリアライゼーションのエンコーディングルールについては、以下を参照: + # シリアライズの符号化規則は以下を参照: # https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ # 二分木の配列表現: # [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] # 二分木の連結リスト表現: - # /——— 15 - # /——— 7 - # /——— 3 - # | \——— 6 - # | \——— 12 + # /——— 15 + # /——— 7 + # /——— 3 + # | \——— 6 + # | \——— 12 # ——— 1 - # \——— 2 - # | /——— 9 - # \——— 4 - # \——— 8 + # \——— 2 + # | /——— 9 + # \——— 4 + # \——— 8 def list_to_tree_dfs(arr: list[int], i: int) -> TreeNode | None: - """リストを二分木にデシリアライズ: 再帰的""" - # インデックスが配列の境界外、または対応する要素がNoneの場合、Noneを返す + """リストを二分木にデシリアライズする: 再帰""" + # 添字が配列長を超えるか、対応する要素が None なら、None を返す if i < 0 or i >= len(arr) or arr[i] is None: return None - # 現在のノードを構築 + # 現在のノードを構築する root = TreeNode(arr[i]) - # 左右の部分木を再帰的に構築 + # 左右の部分木を再帰的に構築する root.left = list_to_tree_dfs(arr, 2 * i + 1) root.right = list_to_tree_dfs(arr, 2 * i + 2) return root def list_to_tree(arr: list[int]) -> TreeNode | None: - """リストを二分木にデシリアライズ""" + """リストを二分木にデシリアライズする""" return list_to_tree_dfs(arr, 0) def tree_to_list_dfs(root: TreeNode, i: int, res: list[int]) -> list[int]: - """二分木をリストにシリアライズ: 再帰的""" + """二分木をリストにシリアライズする: 再帰""" if root is None: return if i >= len(res): @@ -63,7 +63,7 @@ def tree_to_list_dfs(root: TreeNode, i: int, res: list[int]) -> list[int]: def tree_to_list(root: TreeNode | None) -> list[int]: - """二分木をリストにシリアライズ""" + """二分木をリストにシリアライズする""" res = [] tree_to_list_dfs(root, 0, res) - return res \ No newline at end of file + return res diff --git a/ja/codes/python/modules/vertex.py b/ja/codes/python/modules/vertex.py index d052b0e6d..043b84958 100644 --- a/ja/codes/python/modules/vertex.py +++ b/ja/codes/python/modules/vertex.py @@ -11,10 +11,10 @@ class Vertex: def vals_to_vets(vals: list[int]) -> list["Vertex"]: - """値のリストvalsを入力し、頂点のリストvetsを返す""" + """値リスト vals を入力し、頂点リスト vets を返す""" return [Vertex(val) for val in vals] def vets_to_vals(vets: list["Vertex"]) -> list[int]: - """頂点のリストvetsを入力し、値のリストvalsを返す""" - return [vet.val for vet in vets] \ No newline at end of file + """頂点リスト vets を入力し、値リスト vals を返す""" + return [vet.val for vet in vets] diff --git a/ja/codes/python/test_all.py b/ja/codes/python/test_all.py index 6fa7360a1..7c585de87 100644 --- a/ja/codes/python/test_all.py +++ b/ja/codes/python/test_all.py @@ -6,11 +6,11 @@ env = os.environ.copy() env["PYTHONIOENCODING"] = "utf-8" if __name__ == "__main__": - # ソースコードファイルを検索 - src_paths = sorted(glob.glob("ja/codes/python/chapter_*/*.py")) + # find source code files + src_paths = sorted(glob.glob("chapter_*/*.py")) errors = [] - # python コードを実行 + # run python code for src_path in src_paths: process = subprocess.Popen( ["python", src_path], @@ -20,14 +20,14 @@ if __name__ == "__main__": env=env, encoding='utf-8' ) - # プロセスの完了を待ち、出力とエラーメッセージを取得 + # Wait for the process to complete, and get the output and error messages stdout, stderr = process.communicate() - # 終了ステータスをチェック + # Check the exit status exit_status = process.returncode if exit_status != 0: errors.append(stderr) - print(f"{len(src_paths)} ファイルをテストしました") - print(f"{len(errors)} ファイルで例外が見つかりました") + print(f"Tested {len(src_paths)} files") + print(f"Found exception in {len(errors)} files") if len(errors) > 0: - raise RuntimeError("\n\n".join(errors)) \ No newline at end of file + raise RuntimeError("\n\n".join(errors)) diff --git a/ja/codes/pythontutor/chapter_array_and_linkedlist/array.md b/ja/codes/pythontutor/chapter_array_and_linkedlist/array.md new file mode 100644 index 000000000..9799bb8e2 --- /dev/null +++ b/ja/codes/pythontutor/chapter_array_and_linkedlist/array.md @@ -0,0 +1,23 @@ + + + +https://pythontutor.com/render.html#code=import%20random%0A%0Adef%20random_access%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E8%A6%81%E7%B4%A0%E3%81%B8%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%22%22%22%0A%20%20%20%20%23%20%E5%8C%BA%E9%96%93%20%5B0%2C%20len%28nums%29-1%5D%20%E3%81%8B%E3%82%89%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E3%81%AB%E6%95%B0%E5%AD%97%E3%82%92%201%20%E3%81%A4%E9%81%B8%E3%81%B6%0A%20%20%20%20random_index%20%3D%20random.randint%280%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20%23%20%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E3%81%AA%E8%A6%81%E7%B4%A0%E3%82%92%E5%8F%96%E5%BE%97%E3%81%97%E3%81%A6%E8%BF%94%E3%81%99%0A%20%20%20%20random_num%20%3D%20nums%5Brandom_index%5D%0A%20%20%20%20return%20random_num%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%85%8D%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%0A%20%20%20%20random_num%3A%20int%20%3D%20random_access%28nums%29%0A%20%20%20%20print%28%22nums%20%E3%81%8B%E3%82%89%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E3%81%AA%E8%A6%81%E7%B4%A0%E3%82%92%E5%8F%96%E5%BE%97%22%2C%20random_num%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20insert%28nums%3A%20list%5Bint%5D%2C%20num%3A%20int%2C%20index%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E9%85%8D%E5%88%97%E3%81%AE%20index%20%E7%95%AA%E7%9B%AE%E3%81%AB%E8%A6%81%E7%B4%A0%20num%20%E3%82%92%E6%8C%BF%E5%85%A5%22%22%22%0A%20%20%20%20%23%20%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20index%20%E4%BB%A5%E9%99%8D%E3%81%AE%E5%85%A8%E8%A6%81%E7%B4%A0%E3%82%92%201%20%E3%81%A4%E5%BE%8C%E3%82%8D%E3%81%B8%E7%A7%BB%E5%8B%95%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%2C%20index%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20nums%5Bi%20-%201%5D%0A%20%20%20%20%23%20index%20%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%AB%20num%20%E3%82%92%E4%BB%A3%E5%85%A5%E3%81%99%E3%82%8B%0A%20%20%20%20nums%5Bindex%5D%20%3D%20num%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%85%8D%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%82%92%E6%8C%BF%E5%85%A5%E3%81%99%E3%82%8B%0A%20%20%20%20insert%28nums%2C%206%2C%203%29%0A%20%20%20%20print%28%22%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%203%20%E3%81%AB%E6%95%B0%E5%80%A4%206%20%E3%82%92%E6%8C%BF%E5%85%A5%E3%81%97%E3%80%81nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20remove%28nums%3A%20list%5Bint%5D%2C%20index%3A%20int%29%3A%0A%20%20%20%20%22%22%22index%20%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E5%89%8A%E9%99%A4%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20%23%20%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20index%20%E3%82%88%E3%82%8A%E5%BE%8C%E3%82%8D%E3%81%AE%E5%85%A8%E8%A6%81%E7%B4%A0%E3%82%92%201%20%E3%81%A4%E5%89%8D%E3%81%B8%E7%A7%BB%E5%8B%95%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28index%2C%20len%28nums%29%20-%201%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20nums%5Bi%20%2B%201%5D%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%85%8D%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%82%92%E5%89%8A%E9%99%A4%0A%20%20%20%20remove%28nums%2C%202%29%0A%20%20%20%20print%28%22%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%202%20%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E5%89%8A%E9%99%A4%E3%81%97%E3%80%81nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20traverse%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E9%85%8D%E5%88%97%E3%82%92%E8%B5%B0%E6%9F%BB%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%A7%E9%85%8D%E5%88%97%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%E8%A6%81%E7%B4%A0%E3%82%92%E7%9B%B4%E6%8E%A5%E8%B5%B0%E6%9F%BB%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num%0A%20%20%20%20%23%20%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%A8%E8%A6%81%E7%B4%A0%E3%82%92%E5%90%8C%E6%99%82%E3%81%AB%E8%B5%B0%E6%9F%BB%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%2C%20num%20in%20enumerate%28nums%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%85%8D%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20traverse%28nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20find%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%85%8D%E5%88%97%E5%86%85%E3%81%A7%E6%8C%87%E5%AE%9A%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E3%81%99%22%22%22%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20return%20-1%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%85%8D%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E7%B4%A2%E3%81%99%E3%82%8B%0A%20%20%20%20index%3A%20int%20%3D%20find%28nums%2C%203%29%0A%20%20%20%20print%28%22nums%20%E3%81%A7%E8%A6%81%E7%B4%A0%203%20%E3%82%92%E6%A4%9C%E7%B4%A2%E3%81%97%E3%80%81%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20%3D%22%2C%20index%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=%23%20Python%20%E3%81%AE%20list%20%E3%81%AF%E5%8B%95%E7%9A%84%E9%85%8D%E5%88%97%E3%81%A7%E3%81%82%E3%82%8A%E3%80%81%E7%9B%B4%E6%8E%A5%E6%8B%A1%E5%BC%B5%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%0A%23%20%E5%AD%A6%E7%BF%92%E3%81%97%E3%82%84%E3%81%99%E3%81%84%E3%82%88%E3%81%86%E3%80%81%E6%9C%AC%E9%96%A2%E6%95%B0%E3%81%A7%E3%81%AF%20list%20%E3%82%92%E9%95%B7%E3%81%95%E4%B8%8D%E5%A4%89%E3%81%AE%E9%85%8D%E5%88%97%E3%81%A8%E3%81%97%E3%81%A6%E6%89%B1%E3%81%84%E3%81%BE%E3%81%99%0Adef%20extend%28nums%3A%20list%5Bint%5D%2C%20enlarge%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E9%85%8D%E5%88%97%E9%95%B7%E3%82%92%E6%8B%A1%E5%BC%B5%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20%23%20%E6%8B%A1%E5%BC%B5%E5%BE%8C%E3%81%AE%E9%95%B7%E3%81%95%E3%82%92%E6%8C%81%E3%81%A4%E9%85%8D%E5%88%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20%28len%28nums%29%20%2B%20enlarge%29%0A%20%20%20%20%23%20%E5%85%83%E3%81%AE%E9%85%8D%E5%88%97%E3%81%AE%E5%85%A8%E8%A6%81%E7%B4%A0%E3%82%92%E6%96%B0%E3%81%97%E3%81%84%E9%85%8D%E5%88%97%E3%81%AB%E3%82%B3%E3%83%94%E3%83%BC%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20res%5Bi%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%23%20%E6%8B%A1%E5%BC%B5%E5%BE%8C%E3%81%AE%E6%96%B0%E3%81%97%E3%81%84%E9%85%8D%E5%88%97%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20print%28%22%E9%85%8D%E5%88%97%20nums%20%3D%22%2C%20nums%29%0A%0A%20%20%20%20%23%20%E9%95%B7%E3%81%95%E3%82%92%E6%8B%A1%E5%BC%B5%0A%20%20%20%20nums%20%3D%20extend%28nums%2C%203%29%0A%20%20%20%20print%28%22%E9%85%8D%E5%88%97%E3%81%AE%E9%95%B7%E3%81%95%E3%82%92%208%20%E3%81%AB%E6%8B%A1%E5%BC%B5%E3%81%97%E3%80%81nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_array_and_linkedlist/linked_list.md b/ja/codes/pythontutor/chapter_array_and_linkedlist/linked_list.md new file mode 100644 index 000000000..869d064d8 --- /dev/null +++ b/ja/codes/pythontutor/chapter_array_and_linkedlist/linked_list.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B6%9A%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20insert%28n0%3A%20ListNode%2C%20P%3A%20ListNode%29%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%81%A7%E3%83%8E%E3%83%BC%E3%83%89%20n0%20%E3%81%AE%E5%BE%8C%E3%82%8D%E3%81%AB%E3%83%8E%E3%83%BC%E3%83%89%20P%20%E3%82%92%E6%8C%BF%E5%85%A5%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20n1%20%3D%20n0.next%0A%20%20%20%20P.next%20%3D%20n1%0A%20%20%20%20n0.next%20%3D%20P%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E5%90%84%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E9%96%93%E3%81%AE%E5%8F%82%E7%85%A7%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8C%BF%E5%85%A5%0A%20%20%20%20p%20%3D%20ListNode%280%29%0A%20%20%20%20insert%28n0%2C%20p%29&cumulative=false&curInstr=39&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B6%9A%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20remove%28n0%3A%20ListNode%29%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%81%A7%E3%83%8E%E3%83%BC%E3%83%89%20n0%20%E3%81%AE%E7%9B%B4%E5%BE%8C%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%89%8A%E9%99%A4%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20if%20not%20n0.next%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20n0%20-%3E%20P%20-%3E%20n1%0A%20%20%20%20P%20%3D%20n0.next%0A%20%20%20%20n1%20%3D%20P.next%0A%20%20%20%20n0.next%20%3D%20n1%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E5%90%84%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E9%96%93%E3%81%AE%E5%8F%82%E7%85%A7%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%89%8A%E9%99%A4%0A%20%20%20%20remove%28n0%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B6%9A%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20access%28head%3A%20ListNode%2C%20index%3A%20int%29%20-%3E%20ListNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E5%86%85%E3%81%A7%20index%20%E7%95%AA%E7%9B%AE%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%22%22%22%0A%20%20%20%20for%20_%20in%20range%28index%29%3A%0A%20%20%20%20%20%20%20%20if%20not%20head%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20head%20%3D%20head.next%0A%20%20%20%20return%20head%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E5%90%84%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E9%96%93%E3%81%AE%E5%8F%82%E7%85%A7%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%0A%20%20%20%20node%20%3D%20access%28n0%2C%203%29%0A%20%20%20%20print%28%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%203%20%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E5%80%A4%20%3D%20%7B%7D%22.format%28node.val%29%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B6%9A%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20find%28head%3A%20ListNode%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%81%A7%E5%80%A4%E3%81%8C%20target%20%E3%81%AE%E6%9C%80%E5%88%9D%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8E%A2%E3%81%99%22%22%22%0A%20%20%20%20index%20%3D%200%0A%20%20%20%20while%20head%3A%0A%20%20%20%20%20%20%20%20if%20head.val%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20index%0A%20%20%20%20%20%20%20%20head%20%3D%20head.next%0A%20%20%20%20%20%20%20%20index%20%2B%3D%201%0A%20%20%20%20return%20-1%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E5%90%84%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E9%96%93%E3%81%AE%E5%8F%82%E7%85%A7%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4%0A%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8E%A2%E7%B4%A2%0A%20%20%20%20index%20%3D%20find%28n0%2C%202%29%0A%20%20%20%20print%28%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E5%86%85%E3%81%A7%E5%80%A4%E3%81%8C%202%20%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20%3D%20%7B%7D%22.format%28index%29%29&cumulative=false&curInstr=34&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_array_and_linkedlist/my_list.md b/ja/codes/pythontutor/chapter_array_and_linkedlist/my_list.md new file mode 100644 index 000000000..76efa9215 --- /dev/null +++ b/ja/codes/pythontutor/chapter_array_and_linkedlist/my_list.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20MyList%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self._capacity%3A%20int%20%3D%2010%0A%20%20%20%20%20%20%20%20self._arr%3A%20list%5Bint%5D%20%3D%20%5B0%5D%20%2A%20self._capacity%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%20%20%20%20%20%20%20%20self._extend_ratio%3A%20int%20%3D%202%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20capacity%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20self._capacity%0A%0A%20%20%20%20def%20get%28self%2C%20index%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%8C%E7%AF%84%E5%9B%B2%E5%A4%96%E3%81%A7%E3%81%99%27%29%0A%20%20%20%20%20%20%20%20return%20self._arr%5Bindex%5D%0A%0A%20%20%20%20def%20set%28self%2C%20num%3A%20int%2C%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%8C%E7%AF%84%E5%9B%B2%E5%A4%96%E3%81%A7%E3%81%99%27%29%0A%20%20%20%20%20%20%20%20self._arr%5Bindex%5D%20%3D%20num%0A%0A%20%20%20%20def%20add%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20self.size%28%29%20%3D%3D%20self.capacity%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend_capacity%28%29%0A%20%20%20%20%20%20%20%20self._arr%5Bself._size%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%2C%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%8C%E7%AF%84%E5%9B%B2%E5%A4%96%E3%81%A7%E3%81%99%27%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend_capacity%28%29%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28self._size%20-%201%2C%20index%20-%201%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._arr%5Bj%20%2B%201%5D%20%3D%20self._arr%5Bj%5D%0A%20%20%20%20%20%20%20%20self._arr%5Bindex%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20remove%28self%2C%20index%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20index%20%3C%200%20or%20index%20%3E%3D%20self._size%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%8C%E7%AF%84%E5%9B%B2%E5%A4%96%E3%81%A7%E3%81%99%27%29%0A%20%20%20%20%20%20%20%20num%20%3D%20self._arr%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28index%2C%20self._size%20-%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._arr%5Bj%5D%20%3D%20self._arr%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20extend_capacity%28self%29%3A%0A%20%20%20%20%20%20%20%20self._arr%20%3D%20self._arr%20%2B%20%5B0%5D%20%2A%20self.capacity%28%29%20%2A%20%28self._extend_ratio%20-%201%29%0A%20%20%20%20%20%20%20%20self._capacity%20%3D%20len%28self._arr%29%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20nums%20%3D%20MyList%28%29%0A%20%20%20%20nums.add%281%29%0A%20%20%20%20nums.add%283%29%0A%20%20%20%20nums.add%282%29%0A%20%20%20%20nums.add%285%29%0A%20%20%20%20nums.add%284%29%0A%20%20%20%20nums.insert%286%2C%20index%3D3%29%0A%20%20%20%20nums.remove%283%29%0A%20%20%20%20num%20%3D%20nums.get%281%29%0A%20%20%20%20nums.set%280%2C%201%29%0A%20%20%20%20for%20i%20in%20range%2810%29%3A%0A%20%20%20%20%20%20%20%20nums.add%28i%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_backtracking/n_queens.md b/ja/codes/pythontutor/chapter_backtracking/n_queens.md new file mode 100644 index 000000000..991c81433 --- /dev/null +++ b/ja/codes/pythontutor/chapter_backtracking/n_queens.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20row%3A%20int%2C%0A%20%20%20%20n%3A%20int%2C%0A%20%20%20%20state%3A%20list%5Blist%5Bstr%5D%5D%2C%0A%20%20%20%20res%3A%20list%5Blist%5Blist%5Bstr%5D%5D%5D%2C%0A%20%20%20%20cols%3A%20list%5Bbool%5D%2C%0A%20%20%20%20diags1%3A%20list%5Bbool%5D%2C%0A%20%20%20%20diags2%3A%20list%5Bbool%5D%2C%0A%29%3A%0A%20%20%20%20%22%22%22%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0%EF%BC%9AN%20%E3%82%AF%E3%82%A4%E3%83%BC%E3%83%B3%22%22%22%0A%20%20%20%20%23%20%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E8%A1%8C%E3%81%B8%E3%81%AE%E9%85%8D%E7%BD%AE%E3%81%8C%E5%AE%8C%E4%BA%86%E3%81%97%E3%81%9F%E3%82%89%E3%80%81%E8%A7%A3%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%99%E3%82%8B%0A%20%20%20%20if%20row%20%3D%3D%20n%3A%0A%20%20%20%20%20%20%20%20res.append%28%5Blist%28row%29%20for%20row%20in%20state%5D%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E5%88%97%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20for%20col%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E3%81%93%E3%81%AE%E3%83%9E%E3%82%B9%E3%81%AB%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B%E4%B8%BB%E5%AF%BE%E8%A7%92%E7%B7%9A%E3%81%A8%E5%89%AF%E5%AF%BE%E8%A7%92%E7%B7%9A%E3%82%92%E8%A8%88%E7%AE%97%0A%20%20%20%20%20%20%20%20diag1%20%3D%20row%20-%20col%20%2B%20n%20-%201%0A%20%20%20%20%20%20%20%20diag2%20%3D%20row%20%2B%20col%0A%20%20%20%20%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A%EF%BC%9A%E3%81%9D%E3%81%AE%E3%83%9E%E3%82%B9%E3%81%AE%E5%88%97%E3%80%81%E4%B8%BB%E5%AF%BE%E8%A7%92%E7%B7%9A%E3%80%81%E5%89%AF%E5%AF%BE%E8%A7%92%E7%B7%9A%E3%81%AB%E3%82%AF%E3%82%A4%E3%83%BC%E3%83%B3%E3%81%8C%E3%81%82%E3%81%A3%E3%81%A6%E3%81%AF%E3%81%AA%E3%82%89%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20if%20not%20cols%5Bcol%5D%20and%20not%20diags1%5Bdiag1%5D%20and%20not%20diags2%5Bdiag2%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%A9%A6%E8%A1%8C%EF%BC%9A%E3%81%9D%E3%81%AE%E3%83%9E%E3%82%B9%E3%81%AB%E3%82%AF%E3%82%A4%E3%83%BC%E3%83%B3%E3%82%92%E7%BD%AE%E3%81%8F%0A%20%20%20%20%20%20%20%20%20%20%20%20state%5Brow%5D%5Bcol%5D%20%3D%20%22Q%22%0A%20%20%20%20%20%20%20%20%20%20%20%20cols%5Bcol%5D%20%3D%20diags1%5Bdiag1%5D%20%3D%20diags2%5Bdiag2%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%AC%A1%E3%81%AE%E8%A1%8C%E3%81%AB%E9%85%8D%E7%BD%AE%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28row%20%2B%201%2C%20n%2C%20state%2C%20res%2C%20cols%2C%20diags1%2C%20diags2%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%88%BB%E3%81%99%EF%BC%9A%E3%81%9D%E3%81%AE%E3%83%9E%E3%82%B9%E3%82%92%E7%A9%BA%E3%81%8D%E3%83%9E%E3%82%B9%E3%81%AB%E6%88%BB%E3%81%99%0A%20%20%20%20%20%20%20%20%20%20%20%20state%5Brow%5D%5Bcol%5D%20%3D%20%22%23%22%0A%20%20%20%20%20%20%20%20%20%20%20%20cols%5Bcol%5D%20%3D%20diags1%5Bdiag1%5D%20%3D%20diags2%5Bdiag2%5D%20%3D%20False%0A%0A%0Adef%20n_queens%28n%3A%20int%29%20-%3E%20list%5Blist%5Blist%5Bstr%5D%5D%5D%3A%0A%20%20%20%20%22%22%22N%20%E3%82%AF%E3%82%A4%E3%83%BC%E3%83%B3%E3%82%92%E8%A7%A3%E3%81%8F%22%22%22%0A%20%20%20%20%23%20n%2An%20%E3%81%AE%E7%9B%A4%E9%9D%A2%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%E3%80%82%27Q%27%20%E3%81%AF%E3%82%AF%E3%82%A4%E3%83%BC%E3%83%B3%E3%80%81%27%23%27%20%E3%81%AF%E7%A9%BA%E3%81%8D%E3%83%9E%E3%82%B9%E3%82%92%E8%A1%A8%E3%81%99%0A%20%20%20%20state%20%3D%20%5B%5B%22%23%22%20for%20_%20in%20range%28n%29%5D%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20cols%20%3D%20%5BFalse%5D%20%2A%20n%20%20%23%20%E5%88%97%E3%81%AB%E3%82%AF%E3%82%A4%E3%83%BC%E3%83%B3%E3%81%8C%E3%81%82%E3%82%8B%E3%81%8B%E8%A8%98%E9%8C%B2%0A%20%20%20%20diags1%20%3D%20%5BFalse%5D%20%2A%20%282%20%2A%20n%20-%201%29%20%20%23%20%E4%B8%BB%E5%AF%BE%E8%A7%92%E7%B7%9A%E3%81%AB%E3%82%AF%E3%82%A4%E3%83%BC%E3%83%B3%E3%81%8C%E3%81%82%E3%82%8B%E3%81%8B%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20diags2%20%3D%20%5BFalse%5D%20%2A%20%282%20%2A%20n%20-%201%29%20%20%23%20%E5%89%AF%E5%AF%BE%E8%A7%92%E7%B7%9A%E3%81%AB%E3%82%AF%E3%82%A4%E3%83%BC%E3%83%B3%E3%81%8C%E3%81%82%E3%82%8B%E3%81%8B%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%280%2C%20n%2C%20state%2C%20res%2C%20cols%2C%20diags1%2C%20diags2%29%0A%0A%20%20%20%20return%20res%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%20%20%20%20res%20%3D%20n_queens%28n%29%0A%0A%20%20%20%20print%28f%22%E5%85%A5%E5%8A%9B%E3%81%95%E3%82%8C%E3%81%9F%E7%9B%A4%E9%9D%A2%E3%81%AE%E7%B8%A6%E6%A8%AA%E3%81%AE%E9%95%B7%E3%81%95%E3%81%AF%20%7Bn%7D%20%E3%81%A7%E3%81%99%22%29%0A%20%20%20%20print%28f%22%E3%82%AF%E3%82%A4%E3%83%BC%E3%83%B3%E3%81%AE%E9%85%8D%E7%BD%AE%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E3%81%AF%E5%85%A8%E9%83%A8%E3%81%A7%20%7Blen%28res%29%7D%20%E9%80%9A%E3%82%8A%E3%81%A7%E3%81%99%22%29%0A%20%20%20%20for%20state%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%22--------------------%22%29%0A%20%20%20%20%20%20%20%20for%20row%20in%20state%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28row%29&cumulative=false&curInstr=61&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_backtracking/permutations_i.md b/ja/codes/pythontutor/chapter_backtracking/permutations_i.md new file mode 100644 index 000000000..d305f1c81 --- /dev/null +++ b/ja/codes/pythontutor/chapter_backtracking/permutations_i.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20choices%3A%20list%5Bint%5D%2C%20selected%3A%20list%5Bbool%5D%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0%EF%BC%9A%E9%A0%86%E5%88%97%20I%22%22%22%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E3%81%AE%E9%95%B7%E3%81%95%E3%81%8C%E8%A6%81%E7%B4%A0%E6%95%B0%E3%81%AB%E7%AD%89%E3%81%97%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E8%A7%A3%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20if%20len%28state%29%20%3D%3D%20len%28choices%29%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%81%B8%E6%8A%9E%E8%82%A2%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20for%20i%2C%20choice%20in%20enumerate%28choices%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A%EF%BC%9A%E8%A6%81%E7%B4%A0%E3%81%AE%E9%87%8D%E8%A4%87%E9%81%B8%E6%8A%9E%E3%82%92%E8%A8%B1%E5%8F%AF%E3%81%97%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20if%20not%20selected%5Bi%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%A9%A6%E8%A1%8C%3A%20%E9%81%B8%E6%8A%9E%E3%82%92%E8%A1%8C%E3%81%84%E3%80%81%E7%8A%B6%E6%85%8B%E3%82%92%E6%9B%B4%E6%96%B0%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20state.append%28choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%AC%A1%E3%81%AE%E9%81%B8%E6%8A%9E%E3%81%B8%E9%80%B2%E3%82%80%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state%2C%20choices%2C%20selected%2C%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AF%EF%BC%9A%E9%81%B8%E6%8A%9E%E3%82%92%E5%8F%96%E3%82%8A%E6%B6%88%E3%81%97%E3%80%81%E5%89%8D%E3%81%AE%E7%8A%B6%E6%85%8B%E3%81%AB%E6%88%BB%E3%81%99%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20permutations_i%28nums%3A%20list%5Bint%5D%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E5%85%A8%E9%A0%86%E5%88%97%20I%22%22%22%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D%2C%20choices%3Dnums%2C%20selected%3D%5BFalse%5D%20%2A%20len%28nums%29%2C%20res%3Dres%29%0A%20%20%20%20return%20res%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%202%2C%203%5D%0A%0A%20%20%20%20res%20%3D%20permutations_i%28nums%29%0A%0A%20%20%20%20print%28f%22%E5%85%A5%E5%8A%9B%E9%85%8D%E5%88%97%20nums%20%3D%20%7Bnums%7D%22%29%0A%20%20%20%20print%28f%22%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%A0%86%E5%88%97%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=13&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_backtracking/permutations_ii.md b/ja/codes/pythontutor/chapter_backtracking/permutations_ii.md new file mode 100644 index 000000000..bcc2d0c40 --- /dev/null +++ b/ja/codes/pythontutor/chapter_backtracking/permutations_ii.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20choices%3A%20list%5Bint%5D%2C%20selected%3A%20list%5Bbool%5D%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0%EF%BC%9A%E9%A0%86%E5%88%97%20II%22%22%22%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E3%81%AE%E9%95%B7%E3%81%95%E3%81%8C%E8%A6%81%E7%B4%A0%E6%95%B0%E3%81%AB%E7%AD%89%E3%81%97%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E8%A7%A3%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20if%20len%28state%29%20%3D%3D%20len%28choices%29%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%81%B8%E6%8A%9E%E8%82%A2%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20duplicated%20%3D%20set%5Bint%5D%28%29%0A%20%20%20%20for%20i%2C%20choice%20in%20enumerate%28choices%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A%EF%BC%9A%E8%A6%81%E7%B4%A0%E3%81%AE%E9%87%8D%E8%A4%87%E9%81%B8%E6%8A%9E%E3%82%92%E8%A8%B1%E5%8F%AF%E3%81%9B%E3%81%9A%E3%80%81%E5%90%8C%E5%80%A4%E8%A6%81%E7%B4%A0%E3%81%AE%E9%87%8D%E8%A4%87%E9%81%B8%E6%8A%9E%E3%82%82%E8%A8%B1%E5%8F%AF%E3%81%97%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20if%20not%20selected%5Bi%5D%20and%20choice%20not%20in%20duplicated%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%A9%A6%E8%A1%8C%3A%20%E9%81%B8%E6%8A%9E%E3%82%92%E8%A1%8C%E3%81%84%E3%80%81%E7%8A%B6%E6%85%8B%E3%82%92%E6%9B%B4%E6%96%B0%0A%20%20%20%20%20%20%20%20%20%20%20%20duplicated.add%28choice%29%20%20%23%20%E9%81%B8%E6%8A%9E%E6%B8%88%E3%81%BF%E3%81%AE%E8%A6%81%E7%B4%A0%E5%80%A4%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20True%0A%20%20%20%20%20%20%20%20%20%20%20%20state.append%28choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%AC%A1%E3%81%AE%E9%81%B8%E6%8A%9E%E3%81%B8%E9%80%B2%E3%82%80%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state%2C%20choices%2C%20selected%2C%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AF%EF%BC%9A%E9%81%B8%E6%8A%9E%E3%82%92%E5%8F%96%E3%82%8A%E6%B6%88%E3%81%97%E3%80%81%E5%89%8D%E3%81%AE%E7%8A%B6%E6%85%8B%E3%81%AB%E6%88%BB%E3%81%99%0A%20%20%20%20%20%20%20%20%20%20%20%20selected%5Bi%5D%20%3D%20False%0A%20%20%20%20%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20permutations_ii%28nums%3A%20list%5Bint%5D%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E5%85%A8%E9%A0%86%E5%88%97%20II%22%22%22%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D%2C%20choices%3Dnums%2C%20selected%3D%5BFalse%5D%20%2A%20len%28nums%29%2C%20res%3Dres%29%0A%20%20%20%20return%20res%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%202%2C%202%5D%0A%0A%20%20%20%20res%20%3D%20permutations_ii%28nums%29%0A%0A%20%20%20%20print%28f%22%E5%85%A5%E5%8A%9B%E9%85%8D%E5%88%97%20nums%20%3D%20%7Bnums%7D%22%29%0A%20%20%20%20print%28f%22%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%A0%86%E5%88%97%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=13&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_backtracking/preorder_traversal_i_compact.md b/ja/codes/pythontutor/chapter_backtracking/preorder_traversal_i_compact.md new file mode 100644 index 000000000..0ca6ecd8e --- /dev/null +++ b/ja/codes/pythontutor/chapter_backtracking/preorder_traversal_i_compact.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AB%E3%83%87%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8B%3A%20%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E6%B7%BB%E5%AD%97%E3%81%8C%E9%85%8D%E5%88%97%E9%95%B7%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%8B%E3%80%81%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B%E8%A6%81%E7%B4%A0%E3%81%8C%20None%20%E3%81%AA%E3%82%89%E3%80%81None%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E7%8F%BE%E5%9C%A8%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E6%9C%A8%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AB%E3%83%87%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E9%A0%86%E8%B5%B0%E6%9F%BB%EF%BC%9A%E4%BE%8B%E9%A1%8C%201%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%A7%A3%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20%20%20%20%20res.append%28root%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E5%85%88%E8%A1%8C%E9%A0%86%E8%B5%B0%E6%9F%BB%0A%20%20%20%20res%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E5%80%A4%E3%81%8C%207%20%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E3%81%99%E3%81%B9%E3%81%A6%E5%87%BA%E5%8A%9B%22%29%0A%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20res%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_backtracking/preorder_traversal_ii_compact.md b/ja/codes/pythontutor/chapter_backtracking/preorder_traversal_ii_compact.md new file mode 100644 index 000000000..0a676f808 --- /dev/null +++ b/ja/codes/pythontutor/chapter_backtracking/preorder_traversal_ii_compact.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AB%E3%83%87%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8B%3A%20%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E6%B7%BB%E5%AD%97%E3%81%8C%E9%85%8D%E5%88%97%E9%95%B7%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%8B%E3%80%81%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B%E8%A6%81%E7%B4%A0%E3%81%8C%20None%20%E3%81%AA%E3%82%89%E3%80%81None%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E7%8F%BE%E5%9C%A8%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E6%9C%A8%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AB%E3%83%87%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E9%A0%86%E8%B5%B0%E6%9F%BB%EF%BC%9A%E4%BE%8B%E9%A1%8C%202%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%A9%A6%E3%81%99%0A%20%20%20%20path.append%28root%29%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%A7%A3%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20%20%20%20%20res.append%28list%28path%29%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%20%20%20%20%23%20%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AF%0A%20%20%20%20path.pop%28%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E5%85%88%E8%A1%8C%E9%A0%86%E8%B5%B0%E6%9F%BB%0A%20%20%20%20path%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20res%20%3D%20list%5Blist%5BTreeNode%5D%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%E3%81%8B%E3%82%89%E3%83%8E%E3%83%BC%E3%83%89%207%20%E3%81%BE%E3%81%A7%E3%81%AE%E7%B5%8C%E8%B7%AF%E3%82%92%E3%81%99%E3%81%B9%E3%81%A6%E5%87%BA%E5%8A%9B%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_compact.md b/ja/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_compact.md new file mode 100644 index 000000000..92ab34c65 --- /dev/null +++ b/ja/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_compact.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AB%E3%83%87%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8B%3A%20%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E6%B7%BB%E5%AD%97%E3%81%8C%E9%85%8D%E5%88%97%E9%95%B7%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%8B%E3%80%81%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B%E8%A6%81%E7%B4%A0%E3%81%8C%20None%20%E3%81%AA%E3%82%89%E3%80%81None%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E7%8F%BE%E5%9C%A8%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E6%9C%A8%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AB%E3%83%87%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%29%3A%0A%20%20%20%20%22%22%22%E5%89%8D%E9%A0%86%E8%B5%B0%E6%9F%BB%EF%BC%9A%E4%BE%8B%E9%A1%8C%203%22%22%22%0A%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A%0A%20%20%20%20if%20root%20is%20None%20or%20root.val%20%3D%3D%203%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%A9%A6%E3%81%99%0A%20%20%20%20path.append%28root%29%0A%20%20%20%20if%20root.val%20%3D%3D%207%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%A7%A3%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20%20%20%20%20res.append%28list%28path%29%29%0A%20%20%20%20pre_order%28root.left%29%0A%20%20%20%20pre_order%28root.right%29%0A%20%20%20%20%23%20%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AF%0A%20%20%20%20path.pop%28%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E5%85%88%E8%A1%8C%E9%A0%86%E8%B5%B0%E6%9F%BB%0A%20%20%20%20path%20%3D%20list%5BTreeNode%5D%28%29%0A%20%20%20%20res%20%3D%20list%5Blist%5BTreeNode%5D%5D%28%29%0A%20%20%20%20pre_order%28root%29%0A%0A%20%20%20%20print%28%22%5Cn%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%E3%81%8B%E3%82%89%E3%83%8E%E3%83%BC%E3%83%89%207%20%E3%81%BE%E3%81%A7%E3%81%AE%E7%B5%8C%E8%B7%AF%E3%82%92%E3%81%99%E3%81%B9%E3%81%A6%E5%87%BA%E5%8A%9B%E3%81%97%E3%80%81%E7%B5%8C%E8%B7%AF%E3%81%AB%E3%81%AF%E5%80%A4%E3%81%8C%203%20%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%90%AB%E3%82%81%E3%81%AA%E3%81%84%22%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=126&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_template.md b/ja/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_template.md new file mode 100644 index 000000000..4197c42c6 --- /dev/null +++ b/ja/codes/pythontutor/chapter_backtracking/preorder_traversal_iii_template.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%3D0%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0Adef%20is_solution%28state%3A%20list%5BTreeNode%5D%29%20-%3E%20bool%3A%0A%20%20%20%20return%20state%20and%20state%5B-1%5D.val%20%3D%3D%207%0A%0Adef%20record_solution%28state%3A%20list%5BTreeNode%5D%2C%20res%3A%20list%5Blist%5BTreeNode%5D%5D%29%3A%0A%20%20%20%20res.append%28list%28state%29%29%0A%0Adef%20is_valid%28state%3A%20list%5BTreeNode%5D%2C%20choice%3A%20TreeNode%29%20-%3E%20bool%3A%0A%20%20%20%20return%20choice%20is%20not%20None%20and%20choice.val%20%21%3D%203%0A%0Adef%20make_choice%28state%3A%20list%5BTreeNode%5D%2C%20choice%3A%20TreeNode%29%3A%0A%20%20%20%20state.append%28choice%29%0A%0Adef%20undo_choice%28state%3A%20list%5BTreeNode%5D%2C%20choice%3A%20TreeNode%29%3A%0A%20%20%20%20state.pop%28%29%0A%0Adef%20backtrack%28state%3A%20list%5BTreeNode%5D%2C%20choices%3A%20list%5BTreeNode%5D%2C%20res%3A%20list%5Blist%5BTreeNode%5D%5D%29%3A%0A%20%20%20%20if%20is_solution%28state%29%3A%0A%20%20%20%20%20%20%20%20record_solution%28state%2C%20res%29%0A%20%20%20%20for%20choice%20in%20choices%3A%0A%20%20%20%20%20%20%20%20if%20is_valid%28state%2C%20choice%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20make_choice%28state%2C%20choice%29%0A%20%20%20%20%20%20%20%20%20%20%20%20backtrack%28state%2C%20%5Bchoice.left%2C%20choice.right%5D%2C%20res%29%0A%20%20%20%20%20%20%20%20%20%20%20%20undo_choice%28state%2C%20choice%29%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20root%20%3D%20list_to_tree%28%5B1%2C%207%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20backtrack%28state%3D%5B%5D%2C%20choices%3D%5Broot%5D%2C%20res%3Dres%29%0A%20%20%20%20print%28%27%5Cn%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E7%B5%8C%E8%B7%AF%E3%82%92%E5%87%BA%E5%8A%9B%27%29%0A%20%20%20%20for%20path%20in%20res%3A%0A%20%20%20%20%20%20%20%20print%28%5Bnode.val%20for%20node%20in%20path%5D%29&cumulative=false&curInstr=138&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_backtracking/subset_sum_i.md b/ja/codes/pythontutor/chapter_backtracking/subset_sum_i.md new file mode 100644 index 000000000..a5be4484b --- /dev/null +++ b/ja/codes/pythontutor/chapter_backtracking/subset_sum_i.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20target%3A%20int%2C%20choices%3A%20list%5Bint%5D%2C%20start%3A%20int%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0%EF%BC%9A%E9%83%A8%E5%88%86%E5%92%8C%20I%22%22%22%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E5%92%8C%E3%81%8C%20target%20%E3%81%AB%E7%AD%89%E3%81%97%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E8%A7%A3%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20if%20target%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%81%B8%E6%8A%9E%E8%82%A2%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A%202%3A%20start%20%E3%81%8B%E3%82%89%E8%B5%B0%E6%9F%BB%E3%81%97%E3%80%81%E9%87%8D%E8%A4%87%E3%81%99%E3%82%8B%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E7%94%9F%E6%88%90%E3%82%92%E9%81%BF%E3%81%91%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28start%2C%20len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A1%EF%BC%9A%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E5%92%8C%E3%81%8C%20target%20%E3%82%92%E8%B6%85%E3%81%88%E3%81%9F%E3%82%89%E3%80%81%E7%9B%B4%E3%81%A1%E3%81%AB%E3%83%AB%E3%83%BC%E3%83%97%E3%82%92%E7%B5%82%E4%BA%86%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%23%20%E9%85%8D%E5%88%97%E3%81%AF%E3%82%BD%E3%83%BC%E3%83%88%E6%B8%88%E3%81%BF%E3%81%A7%E5%BE%8C%E7%B6%9A%E8%A6%81%E7%B4%A0%E3%81%AE%E3%81%BB%E3%81%86%E3%81%8C%E5%A4%A7%E3%81%8D%E3%81%8F%E3%80%81%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E5%92%8C%E3%81%AF%E5%BF%85%E3%81%9A%20target%20%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%9F%E3%82%81%0A%20%20%20%20%20%20%20%20if%20target%20-%20choices%5Bi%5D%20%3C%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E8%A9%A6%E3%81%99%EF%BC%9A%E9%81%B8%E6%8A%9E%E3%82%92%E8%A1%8C%E3%81%84%E3%80%81target%20%E3%81%A8%20start%20%E3%82%92%E6%9B%B4%E6%96%B0%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E6%AC%A1%E3%81%AE%E9%81%B8%E6%8A%9E%E3%81%B8%E9%80%B2%E3%82%80%0A%20%20%20%20%20%20%20%20backtrack%28state%2C%20target%20-%20choices%5Bi%5D%2C%20choices%2C%20i%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AF%EF%BC%9A%E9%81%B8%E6%8A%9E%E3%82%92%E5%8F%96%E3%82%8A%E6%B6%88%E3%81%97%E3%80%81%E5%89%8D%E3%81%AE%E7%8A%B6%E6%85%8B%E3%81%AB%E6%88%BB%E3%81%99%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_i%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E9%83%A8%E5%88%86%E5%92%8C%20I%20%E3%82%92%E8%A7%A3%E3%81%8F%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8A%B6%E6%85%8B%EF%BC%88%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%EF%BC%89%0A%20%20%20%20nums.sort%28%29%20%20%23%20nums%20%E3%82%92%E3%82%BD%E3%83%BC%E3%83%88%0A%20%20%20%20start%20%3D%200%20%20%23%20%E9%96%8B%E5%A7%8B%E7%82%B9%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%B5%90%E6%9E%9C%E3%83%AA%E3%82%B9%E3%83%88%EF%BC%88%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E3%83%AA%E3%82%B9%E3%83%88%EF%BC%89%0A%20%20%20%20backtrack%28state%2C%20target%2C%20nums%2C%20start%2C%20res%29%0A%20%20%20%20return%20res%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B3%2C%204%2C%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_i%28nums%2C%20target%29%0A%0A%20%20%20%20print%28f%22%E5%85%A5%E5%8A%9B%E9%85%8D%E5%88%97%20nums%20%3D%20%7Bnums%7D%2C%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E5%92%8C%E3%81%8C%20%7Btarget%7D%20%E3%81%AB%E7%AD%89%E3%81%97%E3%81%84%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_backtracking/subset_sum_i_naive.md b/ja/codes/pythontutor/chapter_backtracking/subset_sum_i_naive.md new file mode 100644 index 000000000..287d3b36f --- /dev/null +++ b/ja/codes/pythontutor/chapter_backtracking/subset_sum_i_naive.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%0A%20%20%20%20target%3A%20int%2C%0A%20%20%20%20total%3A%20int%2C%0A%20%20%20%20choices%3A%20list%5Bint%5D%2C%0A%20%20%20%20res%3A%20list%5Blist%5Bint%5D%5D%2C%0A%29%3A%0A%20%20%20%20%22%22%22%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0%EF%BC%9A%E9%83%A8%E5%88%86%E5%92%8C%20I%22%22%22%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E5%92%8C%E3%81%8C%20target%20%E3%81%AB%E7%AD%89%E3%81%97%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E8%A7%A3%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20if%20total%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%81%B8%E6%8A%9E%E8%82%A2%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20for%20i%20in%20range%28len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A%EF%BC%9A%E9%83%A8%E5%88%86%E5%92%8C%E3%81%8C%20target%20%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AF%E3%81%9D%E3%81%AE%E9%81%B8%E6%8A%9E%E3%82%92%E3%82%B9%E3%82%AD%E3%83%83%E3%83%97%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20if%20total%20%2B%20choices%5Bi%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E8%A9%A6%E8%A1%8C%EF%BC%9A%E9%81%B8%E6%8A%9E%E3%82%92%E8%A1%8C%E3%81%84%E3%80%81%E8%A6%81%E7%B4%A0%E3%81%A8%20total%20%E3%82%92%E6%9B%B4%E6%96%B0%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E6%AC%A1%E3%81%AE%E9%81%B8%E6%8A%9E%E3%81%B8%E9%80%B2%E3%82%80%0A%20%20%20%20%20%20%20%20backtrack%28state%2C%20target%2C%20total%20%2B%20choices%5Bi%5D%2C%20choices%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AF%EF%BC%9A%E9%81%B8%E6%8A%9E%E3%82%92%E5%8F%96%E3%82%8A%E6%B6%88%E3%81%97%E3%80%81%E5%89%8D%E3%81%AE%E7%8A%B6%E6%85%8B%E3%81%AB%E6%88%BB%E3%81%99%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_i_naive%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E9%83%A8%E5%88%86%E5%92%8C%20I%20%E3%82%92%E8%A7%A3%E3%81%8F%EF%BC%88%E9%87%8D%E8%A4%87%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%82%92%E5%90%AB%E3%82%80%EF%BC%89%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8A%B6%E6%85%8B%EF%BC%88%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%EF%BC%89%0A%20%20%20%20total%20%3D%200%20%20%23%20%E9%83%A8%E5%88%86%E5%92%8C%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%B5%90%E6%9E%9C%E3%83%AA%E3%82%B9%E3%83%88%EF%BC%88%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E3%83%AA%E3%82%B9%E3%83%88%EF%BC%89%0A%20%20%20%20backtrack%28state%2C%20target%2C%20total%2C%20nums%2C%20res%29%0A%20%20%20%20return%20res%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B3%2C%204%2C%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_i_naive%28nums%2C%20target%29%0A%0A%20%20%20%20print%28f%22%E5%85%A5%E5%8A%9B%E9%85%8D%E5%88%97%20nums%20%3D%20%7Bnums%7D%2C%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E5%92%8C%E3%81%8C%20%7Btarget%7D%20%E3%81%AB%E7%AD%89%E3%81%97%E3%81%84%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%20res%20%3D%20%7Bres%7D%22%29%0A%20%20%20%20print%28f%22%E6%B3%A8%E6%84%8F%3A%20%E3%81%93%E3%81%AE%E6%96%B9%E6%B3%95%E3%81%AE%E5%87%BA%E5%8A%9B%E7%B5%90%E6%9E%9C%E3%81%AB%E3%81%AF%E9%87%8D%E8%A4%87%E3%81%99%E3%82%8B%E9%9B%86%E5%90%88%E3%81%8C%E5%90%AB%E3%81%BE%E3%82%8C%E3%81%BE%E3%81%99%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_backtracking/subset_sum_ii.md b/ja/codes/pythontutor/chapter_backtracking/subset_sum_ii.md new file mode 100644 index 000000000..f3ef2f93e --- /dev/null +++ b/ja/codes/pythontutor/chapter_backtracking/subset_sum_ii.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28%0A%20%20%20%20state%3A%20list%5Bint%5D%2C%20target%3A%20int%2C%20choices%3A%20list%5Bint%5D%2C%20start%3A%20int%2C%20res%3A%20list%5Blist%5Bint%5D%5D%0A%29%3A%0A%20%20%20%20%22%22%22%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0%EF%BC%9A%E9%83%A8%E5%88%86%E5%92%8C%20II%22%22%22%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E5%92%8C%E3%81%8C%20target%20%E3%81%AB%E7%AD%89%E3%81%97%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E8%A7%A3%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20if%20target%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20res.append%28list%28state%29%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%81%B8%E6%8A%9E%E8%82%A2%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A%202%3A%20start%20%E3%81%8B%E3%82%89%E8%B5%B0%E6%9F%BB%E3%81%97%E3%80%81%E9%87%8D%E8%A4%87%E3%81%99%E3%82%8B%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E7%94%9F%E6%88%90%E3%82%92%E9%81%BF%E3%81%91%E3%82%8B%0A%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A%203%3A%20start%20%E3%81%8B%E3%82%89%E8%B5%B0%E6%9F%BB%E3%81%97%E3%80%81%E5%90%8C%E3%81%98%E8%A6%81%E7%B4%A0%E3%81%AE%E9%87%8D%E8%A4%87%E9%81%B8%E6%8A%9E%E3%82%92%E9%81%BF%E3%81%91%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28start%2C%20len%28choices%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A1%EF%BC%9A%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E5%92%8C%E3%81%8C%20target%20%E3%82%92%E8%B6%85%E3%81%88%E3%81%9F%E3%82%89%E3%80%81%E7%9B%B4%E3%81%A1%E3%81%AB%E3%83%AB%E3%83%BC%E3%83%97%E3%82%92%E7%B5%82%E4%BA%86%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%23%20%E9%85%8D%E5%88%97%E3%81%AF%E3%82%BD%E3%83%BC%E3%83%88%E6%B8%88%E3%81%BF%E3%81%A7%E5%BE%8C%E7%B6%9A%E8%A6%81%E7%B4%A0%E3%81%AE%E3%81%BB%E3%81%86%E3%81%8C%E5%A4%A7%E3%81%8D%E3%81%8F%E3%80%81%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E5%92%8C%E3%81%AF%E5%BF%85%E3%81%9A%20target%20%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%9F%E3%82%81%0A%20%20%20%20%20%20%20%20if%20target%20-%20choices%5Bi%5D%20%3C%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A4%EF%BC%9A%E3%81%93%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%8C%E5%B7%A6%E9%9A%A3%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%A8%E7%AD%89%E3%81%97%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E3%81%9D%E3%81%AE%E6%8E%A2%E7%B4%A2%E5%88%86%E5%B2%90%E3%81%AF%E9%87%8D%E8%A4%87%E3%81%97%E3%81%A6%E3%81%84%E3%82%8B%E3%81%9F%E3%82%81%E3%82%B9%E3%82%AD%E3%83%83%E3%83%97%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20if%20i%20%3E%20start%20and%20choices%5Bi%5D%20%3D%3D%20choices%5Bi%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E8%A9%A6%E3%81%99%EF%BC%9A%E9%81%B8%E6%8A%9E%E3%82%92%E8%A1%8C%E3%81%84%E3%80%81target%20%E3%81%A8%20start%20%E3%82%92%E6%9B%B4%E6%96%B0%0A%20%20%20%20%20%20%20%20state.append%28choices%5Bi%5D%29%0A%20%20%20%20%20%20%20%20%23%20%E6%AC%A1%E3%81%AE%E9%81%B8%E6%8A%9E%E3%81%B8%E9%80%B2%E3%82%80%0A%20%20%20%20%20%20%20%20backtrack%28state%2C%20target%20-%20choices%5Bi%5D%2C%20choices%2C%20i%20%2B%201%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AF%EF%BC%9A%E9%81%B8%E6%8A%9E%E3%82%92%E5%8F%96%E3%82%8A%E6%B6%88%E3%81%97%E3%80%81%E5%89%8D%E3%81%AE%E7%8A%B6%E6%85%8B%E3%81%AB%E6%88%BB%E3%81%99%0A%20%20%20%20%20%20%20%20state.pop%28%29%0A%0A%0Adef%20subset_sum_ii%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Blist%5Bint%5D%5D%3A%0A%20%20%20%20%22%22%22%E9%83%A8%E5%88%86%E5%92%8C%20II%20%E3%82%92%E8%A7%A3%E3%81%8F%22%22%22%0A%20%20%20%20state%20%3D%20%5B%5D%20%20%23%20%E7%8A%B6%E6%85%8B%EF%BC%88%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%EF%BC%89%0A%20%20%20%20nums.sort%28%29%20%20%23%20nums%20%E3%82%92%E3%82%BD%E3%83%BC%E3%83%88%0A%20%20%20%20start%20%3D%200%20%20%23%20%E9%96%8B%E5%A7%8B%E7%82%B9%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20res%20%3D%20%5B%5D%20%20%23%20%E7%B5%90%E6%9E%9C%E3%83%AA%E3%82%B9%E3%83%88%EF%BC%88%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%E3%81%AE%E3%83%AA%E3%82%B9%E3%83%88%EF%BC%89%0A%20%20%20%20backtrack%28state%2C%20target%2C%20nums%2C%20start%2C%20res%29%0A%20%20%20%20return%20res%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%204%2C%205%5D%0A%20%20%20%20target%20%3D%209%0A%20%20%20%20res%20%3D%20subset_sum_ii%28nums%2C%20target%29%0A%0A%20%20%20%20print%28f%22%E5%85%A5%E5%8A%9B%E9%85%8D%E5%88%97%20nums%20%3D%20%7Bnums%7D%2C%20target%20%3D%20%7Btarget%7D%22%29%0A%20%20%20%20print%28f%22%E5%92%8C%E3%81%8C%20%7Btarget%7D%20%E3%81%AB%E7%AD%89%E3%81%97%E3%81%84%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%83%A8%E5%88%86%E9%9B%86%E5%90%88%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_computational_complexity/iteration.md b/ja/codes/pythontutor/chapter_computational_complexity/iteration.md new file mode 100644 index 000000000..c95a2713e --- /dev/null +++ b/ja/codes/pythontutor/chapter_computational_complexity/iteration.md @@ -0,0 +1,29 @@ + + + +https://pythontutor.com/render.html#code=def%20for_loop%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22for%20%E3%83%AB%E3%83%BC%E3%83%97%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%201%2C%202%2C%20...%2C%20n-1%2C%20n%20%E3%82%92%E9%A0%86%E3%81%AB%E5%8A%A0%E7%AE%97%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop%28n%29%0A%20%20%20%20print%28f%22%5Cnfor%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%AE%E5%90%88%E8%A8%88%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D& + + +https://pythontutor.com/render.html#code=def%20while_loop%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22while%20%E3%83%AB%E3%83%BC%E3%83%97%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20i%20%3D%201%20%20%23%20%E6%9D%A1%E4%BB%B6%E5%A4%89%E6%95%B0%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%23%201%2C%202%2C%20...%2C%20n-1%2C%20n%20%E3%82%92%E9%A0%86%E3%81%AB%E5%8A%A0%E7%AE%97%E3%81%99%E3%82%8B%0A%20%20%20%20while%20i%20%3C%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E6%9D%A1%E4%BB%B6%E5%A4%89%E6%95%B0%E3%82%92%E6%9B%B4%E6%96%B0%E3%81%99%E3%82%8B%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20while_loop%28n%29%0A%20%20%20%20print%28f%22%5Cnwhile%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%AE%E5%90%88%E8%A8%88%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20while_loop_ii%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22while%20%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%882%E5%9B%9E%E6%9B%B4%E6%96%B0%EF%BC%89%22%22%22%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20i%20%3D%201%20%20%23%20%E6%9D%A1%E4%BB%B6%E5%A4%89%E6%95%B0%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%23%201%2C%204%2C%2010%2C%20...%20%E3%82%92%E9%A0%86%E3%81%AB%E5%8A%A0%E7%AE%97%E3%81%99%E3%82%8B%0A%20%20%20%20while%20i%20%3C%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20i%0A%20%20%20%20%20%20%20%20%23%20%E6%9D%A1%E4%BB%B6%E5%A4%89%E6%95%B0%E3%82%92%E6%9B%B4%E6%96%B0%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20i%20%2A%3D%202%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20while_loop_ii%28n%29%0A%20%20%20%20print%28f%22%5Cnwhile%20%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%882%20%E5%9B%9E%E6%9B%B4%E6%96%B0%EF%BC%89%E3%81%AE%E5%90%88%E8%A8%88%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20nested_for_loop%28n%3A%20int%29%20-%3E%20str%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E9%87%8D%20for%20%E3%83%AB%E3%83%BC%E3%83%97%22%22%22%0A%20%20%20%20res%20%3D%20%22%22%0A%20%20%20%20%23%20i%20%3D%201%2C%202%2C%20...%2C%20n-1%2C%20n%20%E3%81%A8%E3%83%AB%E3%83%BC%E3%83%97%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20j%20%3D%201%2C%202%2C%20...%2C%20n-1%2C%20n%20%E3%81%A8%E3%83%AB%E3%83%BC%E3%83%97%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20f%22%28%7Bi%7D%2C%20%7Bj%7D%29%2C%20%22%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20nested_for_loop%28n%29%0A%20%20%20%20print%28f%22%5Cn%E4%BA%8C%E9%87%8D%20for%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%AE%E8%B5%B0%E6%9F%BB%E7%B5%90%E6%9E%9C%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E4%BA%86%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E5%86%8D%E5%B8%B0%EF%BC%9A%E5%86%8D%E5%B8%B0%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%0A%20%20%20%20res%20%3D%20recur%28n%20-%201%29%0A%20%20%20%20%23%20%E5%B8%B0%E3%82%8A%E3%81%8C%E3%81%91%EF%BC%9A%E7%B5%90%E6%9E%9C%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20n%20%2B%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E5%86%8D%E5%B8%B0%E9%96%A2%E6%95%B0%E3%81%AE%E5%90%88%E8%A8%88%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20tail_recur%28n%2C%20res%29%3A%0A%20%20%20%20%22%22%22%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E4%BA%86%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20res%0A%20%20%20%20%23%20%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%0A%20%20%20%20return%20tail_recur%28n%20-%201%2C%20res%20%2B%20n%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20tail_recur%28n%2C%200%29%0A%20%20%20%20print%28f%22%5Cn%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%E9%96%A2%E6%95%B0%E3%81%AE%E5%90%88%E8%A8%88%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20fib%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E3%83%95%E3%82%A3%E3%83%9C%E3%83%8A%E3%83%83%E3%83%81%E6%95%B0%E5%88%97%EF%BC%9A%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E4%BA%86%E6%9D%A1%E4%BB%B6%20f%281%29%20%3D%200%2C%20f%282%29%20%3D%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%20-%201%0A%20%20%20%20%23%20f%28n%29%20%3D%20f%28n-1%29%20%2B%20f%28n-2%29%20%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%99%0A%20%20%20%20res%20%3D%20fib%28n%20-%201%29%20%2B%20fib%28n%20-%202%29%0A%20%20%20%20%23%20%E7%B5%90%E6%9E%9C%20f%28n%29%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20fib%28n%29%0A%20%20%20%20print%28f%22%5Cn%E3%83%95%E3%82%A3%E3%83%9C%E3%83%8A%E3%83%83%E3%83%81%E6%95%B0%E5%88%97%E3%81%AE%E7%AC%AC%20%7Bn%7D%20%E9%A0%85%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20for_loop_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%8F%8D%E5%BE%A9%E3%81%A7%E5%86%8D%E5%B8%B0%E3%82%92%E6%A8%A1%E6%93%AC%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20%23%20%E6%98%8E%E7%A4%BA%E7%9A%84%E3%81%AA%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%82%B3%E3%83%BC%E3%83%AB%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%82%92%E6%A8%A1%E6%93%AC%E3%81%99%E3%82%8B%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E5%86%8D%E5%B8%B0%EF%BC%9A%E5%86%8D%E5%B8%B0%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%0A%20%20%20%20for%20i%20in%20range%28n%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E3%80%8C%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%B8%E3%81%AE%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%E3%80%8D%E3%81%A7%E3%80%8C%E5%86%8D%E5%B8%B0%E3%80%8D%E3%82%92%E6%A8%A1%E6%93%AC%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20stack.append%28i%29%0A%20%20%20%20%23%20%E5%B8%B0%E3%82%8A%E3%81%8C%E3%81%91%EF%BC%9A%E7%B5%90%E6%9E%9C%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20while%20stack%3A%0A%20%20%20%20%20%20%20%20%23%20%E3%80%8C%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%8B%E3%82%89%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%99%E6%93%8D%E4%BD%9C%E3%80%8D%E3%81%A7%E3%80%8C%E5%B8%B0%E3%82%8A%E3%80%8D%E3%82%92%E3%82%B7%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%83%88%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20stack.pop%28%29%0A%20%20%20%20%23%20res%20%3D%201%2B2%2B3%2B...%2Bn%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop_recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E5%8F%8D%E5%BE%A9%E3%81%A7%E5%86%8D%E5%B8%B0%E3%82%92%E3%82%B7%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%83%88%E3%81%97%E3%81%9F%E5%90%88%E8%A8%88%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_computational_complexity/recursion.md b/ja/codes/pythontutor/chapter_computational_complexity/recursion.md new file mode 100644 index 000000000..55cef7d9d --- /dev/null +++ b/ja/codes/pythontutor/chapter_computational_complexity/recursion.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=def%20recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E4%BA%86%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E5%86%8D%E5%B8%B0%EF%BC%9A%E5%86%8D%E5%B8%B0%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%0A%20%20%20%20res%20%3D%20recur%28n%20-%201%29%0A%20%20%20%20%23%20%E5%B8%B0%E3%82%8A%E3%81%8C%E3%81%91%EF%BC%9A%E7%B5%90%E6%9E%9C%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20n%20%2B%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E5%86%8D%E5%B8%B0%E9%96%A2%E6%95%B0%E3%81%AE%E5%90%88%E8%A8%88%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20tail_recur%28n%2C%20res%29%3A%0A%20%20%20%20%22%22%22%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E4%BA%86%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20res%0A%20%20%20%20%23%20%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%0A%20%20%20%20return%20tail_recur%28n%20-%201%2C%20res%20%2B%20n%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20tail_recur%28n%2C%200%29%0A%20%20%20%20print%28f%22%5Cn%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%E9%96%A2%E6%95%B0%E3%81%AE%E5%90%88%E8%A8%88%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20fib%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E3%83%95%E3%82%A3%E3%83%9C%E3%83%8A%E3%83%83%E3%83%81%E6%95%B0%E5%88%97%EF%BC%9A%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E4%BA%86%E6%9D%A1%E4%BB%B6%20f%281%29%20%3D%200%2C%20f%282%29%20%3D%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%20-%201%0A%20%20%20%20%23%20f%28n%29%20%3D%20f%28n-1%29%20%2B%20f%28n-2%29%20%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%99%0A%20%20%20%20res%20%3D%20fib%28n%20-%201%29%20%2B%20fib%28n%20-%202%29%0A%20%20%20%20%23%20%E7%B5%90%E6%9E%9C%20f%28n%29%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20fib%28n%29%0A%20%20%20%20print%28f%22%5Cn%E3%83%95%E3%82%A3%E3%83%9C%E3%83%8A%E3%83%83%E3%83%81%E6%95%B0%E5%88%97%E3%81%AE%E7%AC%AC%20%7Bn%7D%20%E9%A0%85%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20for_loop_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%8F%8D%E5%BE%A9%E3%81%A7%E5%86%8D%E5%B8%B0%E3%82%92%E6%A8%A1%E6%93%AC%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20%23%20%E6%98%8E%E7%A4%BA%E7%9A%84%E3%81%AA%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%82%B3%E3%83%BC%E3%83%AB%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%82%92%E6%A8%A1%E6%93%AC%E3%81%99%E3%82%8B%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%20%E5%86%8D%E5%B8%B0%EF%BC%9A%E5%86%8D%E5%B8%B0%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%0A%20%20%20%20for%20i%20in%20range%28n%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E3%80%8C%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%B8%E3%81%AE%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%E3%80%8D%E3%81%A7%E3%80%8C%E5%86%8D%E5%B8%B0%E3%80%8D%E3%82%92%E6%A8%A1%E6%93%AC%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20stack.append%28i%29%0A%20%20%20%20%23%20%E5%B8%B0%E3%82%8A%E3%81%8C%E3%81%91%EF%BC%9A%E7%B5%90%E6%9E%9C%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20while%20stack%3A%0A%20%20%20%20%20%20%20%20%23%20%E3%80%8C%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%8B%E3%82%89%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%99%E6%93%8D%E4%BD%9C%E3%80%8D%E3%81%A7%E3%80%8C%E5%B8%B0%E3%82%8A%E3%80%8D%E3%82%92%E3%82%B7%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%83%88%0A%20%20%20%20%20%20%20%20res%20%2B%3D%20stack.pop%28%29%0A%20%20%20%20%23%20res%20%3D%201%2B2%2B3%2B...%2Bn%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20res%20%3D%20for_loop_recur%28n%29%0A%20%20%20%20print%28f%22%5Cn%E5%8F%8D%E5%BE%A9%E3%81%A7%E5%86%8D%E5%B8%B0%E3%82%92%E3%82%B7%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%83%88%E3%81%97%E3%81%9F%E5%90%88%E8%A8%88%E7%B5%90%E6%9E%9C%20res%20%3D%20%7Bres%7D%22%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_computational_complexity/space_complexity.md b/ja/codes/pythontutor/chapter_computational_complexity/space_complexity.md new file mode 100644 index 000000000..065cda9cd --- /dev/null +++ b/ja/codes/pythontutor/chapter_computational_complexity/space_complexity.md @@ -0,0 +1,23 @@ + + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B6%9A%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20function%28%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%96%A2%E6%95%B0%22%22%22%0A%20%20%20%20%23%20%E4%BD%95%E3%82%89%E3%81%8B%E3%81%AE%E5%87%A6%E7%90%86%E3%82%92%E8%A1%8C%E3%81%86%0A%20%20%20%20return%200%0A%0Adef%20constant%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%AE%9A%E6%95%B0%E9%9A%8E%22%22%22%0A%20%20%20%20%23%20%E5%AE%9A%E6%95%B0%E3%80%81%E5%A4%89%E6%95%B0%E3%80%81%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AF%20O%281%29%20%E3%81%AE%E7%A9%BA%E9%96%93%E3%82%92%E5%8D%A0%E3%82%81%E3%82%8B%0A%20%20%20%20a%20%3D%200%0A%20%20%20%20nums%20%3D%20%5B0%5D%20%2A%2010%0A%20%20%20%20node%20%3D%20ListNode%280%29%0A%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E5%86%85%E3%81%AE%E5%A4%89%E6%95%B0%E3%81%AF%20O%281%29%20%E3%81%AE%E7%A9%BA%E9%96%93%E3%82%92%E5%8D%A0%E3%82%81%E3%82%8B%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20c%20%3D%200%0A%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E5%86%85%E3%81%AE%E9%96%A2%E6%95%B0%E3%81%AF%20O%281%29%20%E3%81%AE%E7%A9%BA%E9%96%93%E3%82%92%E5%8D%A0%E3%82%81%E3%82%8B%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20function%28%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E5%AE%9A%E6%95%B0%E9%9A%8E%0A%20%20%20%20constant%28n%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20linear%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E7%B7%9A%E5%BD%A2%E9%9A%8E%22%22%22%0A%20%20%20%20%23%20%E9%95%B7%E3%81%95%20n%20%E3%81%AE%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AF%20O%28n%29%20%E3%81%AE%E7%A9%BA%E9%96%93%E3%82%92%E4%BD%BF%E7%94%A8%0A%20%20%20%20nums%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20%23%20%E9%95%B7%E3%81%95%20n%20%E3%81%AE%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%81%AF%20O%28n%29%20%E3%81%AE%E7%A9%BA%E9%96%93%E3%82%92%E4%BD%BF%E7%94%A8%0A%20%20%20%20hmap%20%3D%20dict%5Bint%2C%20str%5D%28%29%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20hmap%5Bi%5D%20%3D%20str%28i%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E7%B7%9A%E5%BD%A2%E9%9A%8E%0A%20%20%20%20linear%28n%29&cumulative=false&curInstr=20&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20linear_recur%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E7%B7%9A%E5%BD%A2%E6%99%82%E9%96%93%EF%BC%88%E5%86%8D%E5%B8%B0%E5%AE%9F%E8%A3%85%EF%BC%89%22%22%22%0A%20%20%20%20print%28%22%E5%86%8D%E5%B8%B0%20n%20%3D%22%2C%20n%29%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20linear_recur%28n%20-%201%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E7%B7%9A%E5%BD%A2%E9%9A%8E%0A%20%20%20%20linear_recur%28n%29&cumulative=false&curInstr=25&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20quadratic%28n%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E4%B9%97%E9%9A%8E%22%22%22%0A%20%20%20%20%23%20%E4%BA%8C%E6%AC%A1%E5%85%83%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AF%20O%28n%5E2%29%20%E3%81%AE%E7%A9%BA%E9%96%93%E3%82%92%E4%BD%BF%E7%94%A8%0A%20%20%20%20num_matrix%20%3D%20%5B%5B0%5D%20%2A%20n%20for%20_%20in%20range%28n%29%5D%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E4%BA%8C%E4%B9%97%E9%9A%8E%0A%20%20%20%20quadratic%28n%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20quadratic_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E6%AC%A1%E6%99%82%E9%96%93%EF%BC%88%E5%86%8D%E5%B8%B0%E5%AE%9F%E8%A3%85%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%20nums%20%E3%81%AE%E9%95%B7%E3%81%95%E3%81%AF%20n%2C%20n-1%2C%20...%2C%202%2C%201%0A%20%20%20%20nums%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20return%20quadratic_recur%28n%20-%201%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E4%BA%8C%E4%B9%97%E9%9A%8E%0A%20%20%20%20quadratic_recur%28n%29&cumulative=false&curInstr=28&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20build_tree%28n%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B0%E6%99%82%E9%96%93%EF%BC%88%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AE%E6%A7%8B%E7%AF%89%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20root%20%3D%20TreeNode%280%29%0A%20%20%20%20root.left%20%3D%20build_tree%28n%20-%201%29%0A%20%20%20%20root.right%20%3D%20build_tree%28n%20-%201%29%0A%20%20%20%20return%20root%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%205%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20%23%20%E6%8C%87%E6%95%B0%E3%82%AA%E3%83%BC%E3%83%80%E3%83%BC%0A%20%20%20%20root%20%3D%20build_tree%28n%29&cumulative=false&curInstr=507&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_computational_complexity/time_complexity.md b/ja/codes/pythontutor/chapter_computational_complexity/time_complexity.md new file mode 100644 index 000000000..478aaa151 --- /dev/null +++ b/ja/codes/pythontutor/chapter_computational_complexity/time_complexity.md @@ -0,0 +1,38 @@ + + + +https://pythontutor.com/render.html#code=def%20constant%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AE%9A%E6%95%B0%E9%9A%8E%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20size%20%3D%2010%0A%20%20%20%20for%20_%20in%20range%28size%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20constant%28n%29%0A%20%20%20%20print%28%22%E5%AE%9A%E6%95%B0%E6%99%82%E9%96%93%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20linear%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%B7%9A%E5%BD%A2%E9%9A%8E%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20linear%28n%29%0A%20%20%20%20print%28%22%E7%B7%9A%E5%BD%A2%E6%99%82%E9%96%93%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20array_traversal%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%B7%9A%E5%BD%A2%E6%99%82%E9%96%93%EF%BC%88%E9%85%8D%E5%88%97%E3%82%92%E8%B5%B0%E6%9F%BB%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E5%9B%9E%E6%95%B0%E3%81%AF%E9%85%8D%E5%88%97%E9%95%B7%E3%81%AB%E6%AF%94%E4%BE%8B%E3%81%99%E3%82%8B%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20array_traversal%28%5B0%5D%20%2A%20n%29%0A%20%20%20%20print%28%22%E7%B7%9A%E5%BD%A2%E6%99%82%E9%96%93%EF%BC%88%E9%85%8D%E5%88%97%E8%B5%B0%E6%9F%BB%EF%BC%89%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20quadratic%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E4%B9%97%E9%9A%8E%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E5%9B%9E%E6%95%B0%E3%81%AF%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%E3%81%AE%E4%BA%8C%E4%B9%97%E3%81%AB%E6%AF%94%E4%BE%8B%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20quadratic%28n%29%0A%20%20%20%20print%28%22%E4%BA%8C%E4%B9%97%E6%99%82%E9%96%93%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E6%AC%A1%E6%99%82%E9%96%93%EF%BC%88%E3%83%90%E3%83%96%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%20%20%23%20%E3%82%AB%E3%82%A6%E3%83%B3%E3%82%BF%0A%20%20%20%20%23%20%E5%A4%96%E5%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%E3%81%AF%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%20%5B0%2C%20i%5D%20%E3%81%AE%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%E3%82%92%E3%81%9D%E3%81%AE%E5%8C%BA%E9%96%93%E3%81%AE%E6%9C%80%E5%8F%B3%E7%AB%AF%E3%81%B8%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20nums%5Bj%5D%20%E3%81%A8%20nums%5Bj%20%2B%201%5D%20%E3%82%92%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%20%3D%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20tmp%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%203%20%20%23%20%E8%A6%81%E7%B4%A0%E4%BA%A4%E6%8F%9B%E3%81%AB%E3%81%AF%203%20%E5%9B%9E%E3%81%AE%E5%8D%98%E4%BD%8D%E6%93%8D%E4%BD%9C%E3%81%8C%E5%90%AB%E3%81%BE%E3%82%8C%E3%82%8B%0A%20%20%20%20return%20count%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20nums%20%3D%20%5Bi%20for%20i%20in%20range%28n%2C%200%2C%20-1%29%5D%20%20%23%20%5Bn%2C%20n-1%2C%20...%2C%202%2C%201%5D%0A%20%20%20%20count%20%3D%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%E4%BA%8C%E4%B9%97%E6%99%82%E9%96%93%EF%BC%88%E3%83%90%E3%83%96%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%89%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20exponential%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B0%E6%99%82%E9%96%93%EF%BC%88%E3%83%AB%E3%83%BC%E3%83%97%E5%AE%9F%E8%A3%85%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20base%20%3D%201%0A%20%20%20%20%23%20%E7%B4%B0%E8%83%9E%E3%81%AF%E5%90%84%E3%83%A9%E3%82%A6%E3%83%B3%E3%83%89%E3%81%A7%202%20%E3%81%A4%E3%81%AB%E5%88%86%E8%A3%82%E3%81%97%E3%80%81%E6%95%B0%E5%88%97%201%2C%202%2C%204%2C%208%2C%20...%2C%202%5E%28n-1%29%20%E3%82%92%E5%BD%A2%E6%88%90%E3%81%99%E3%82%8B%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20for%20_%20in%20range%28base%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20%20%20%20%20base%20%2A%3D%202%0A%20%20%20%20%23%20count%20%3D%201%20%2B%202%20%2B%204%20%2B%208%20%2B%20..%20%2B%202%5E%28n-1%29%20%3D%202%5En%20-%201%0A%20%20%20%20return%20count%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20exponential%28n%29%0A%20%20%20%20print%28%22%E6%8C%87%E6%95%B0%E6%99%82%E9%96%93%EF%BC%88%E3%83%AB%E3%83%BC%E3%83%97%E5%AE%9F%E8%A3%85%EF%BC%89%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20exp_recur%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%8C%87%E6%95%B0%E6%99%82%E9%96%93%EF%BC%88%E5%86%8D%E5%B8%B0%E5%AE%9F%E8%A3%85%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20return%20exp_recur%28n%20-%201%29%20%2B%20exp_recur%28n%20-%201%29%20%2B%201%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%207%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20exp_recur%28n%29%0A%20%20%20%20print%28%22%E6%8C%87%E6%95%B0%E6%99%82%E9%96%93%EF%BC%88%E5%86%8D%E5%B8%B0%E5%AE%9F%E8%A3%85%EF%BC%89%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20logarithmic%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AF%BE%E6%95%B0%E6%99%82%E9%96%93%EF%BC%88%E3%83%AB%E3%83%BC%E3%83%97%E5%AE%9F%E8%A3%85%EF%BC%89%22%22%22%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20while%20n%20%3E%201%3A%0A%20%20%20%20%20%20%20%20n%20%3D%20n%20%2F%202%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20logarithmic%28n%29%0A%20%20%20%20print%28%22%E5%AF%BE%E6%95%B0%E6%99%82%E9%96%93%EF%BC%88%E3%83%AB%E3%83%BC%E3%83%97%E5%AE%9F%E8%A3%85%EF%BC%89%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20log_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AF%BE%E6%95%B0%E6%99%82%E9%96%93%EF%BC%88%E5%86%8D%E5%B8%B0%E5%AE%9F%E8%A3%85%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%201%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20return%20log_recur%28n%20%2F%202%29%20%2B%201%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20log_recur%28n%29%0A%20%20%20%20print%28%22%E5%AF%BE%E6%95%B0%E6%99%82%E9%96%93%EF%BC%88%E5%86%8D%E5%B8%B0%E5%AE%9F%E8%A3%85%EF%BC%89%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20linear_log_recur%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%B7%9A%E5%BD%A2%E5%AF%BE%E6%95%B0%E6%99%82%E9%96%93%22%22%22%0A%20%20%20%20if%20n%20%3C%3D%201%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20count%20%3D%20linear_log_recur%28n%20%2F%2F%202%29%20%2B%20linear_log_recur%28n%20%2F%2F%202%29%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20return%20count%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%208%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20linear_log_recur%28n%29%0A%20%20%20%20print%28%22%E7%B7%9A%E5%BD%A2%E5%AF%BE%E6%95%B0%E6%99%82%E9%96%93%EF%BC%88%E5%86%8D%E5%B8%B0%E5%AE%9F%E8%A3%85%EF%BC%89%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20factorial_recur%28n%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8E%E4%B9%97%E6%99%82%E9%96%93%EF%BC%88%E5%86%8D%E5%B8%B0%E5%AE%9F%E8%A3%85%EF%BC%89%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%201%E5%80%8B%E3%81%8B%E3%82%89%20n%20%E5%80%8B%E3%81%AB%E5%88%86%E8%A3%82%0A%20%20%20%20for%20_%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20factorial_recur%28n%20-%201%29%0A%20%20%20%20return%20count%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%20%20%20%20print%28%22%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%BA%20n%20%3D%22%2C%20n%29%0A%0A%20%20%20%20count%20%3D%20factorial_recur%28n%29%0A%20%20%20%20print%28%22%E9%9A%8E%E4%B9%97%E6%99%82%E9%96%93%EF%BC%88%E5%86%8D%E5%B8%B0%E5%AE%9F%E8%A3%85%EF%BC%89%E3%81%AE%E6%93%8D%E4%BD%9C%E5%9B%9E%E6%95%B0%20%3D%22%2C%20count%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_computational_complexity/worst_best_time_complexity.md b/ja/codes/pythontutor/chapter_computational_complexity/worst_best_time_complexity.md new file mode 100644 index 000000000..96aa2b2f5 --- /dev/null +++ b/ja/codes/pythontutor/chapter_computational_complexity/worst_best_time_complexity.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=import%20random%0A%0Adef%20random_numbers%28n%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E8%A6%81%E7%B4%A0%E3%81%8C%201%2C%202%2C%20...%2C%20n%20%E3%81%A7%E9%A0%86%E5%BA%8F%E3%81%8C%E3%82%B7%E3%83%A3%E3%83%83%E3%83%95%E3%83%AB%E3%81%95%E3%82%8C%E3%81%9F%E9%85%8D%E5%88%97%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%20nums%20%3D%3A%201%2C%202%2C%203%2C%20...%2C%20n%20%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B%0A%20%20%20%20nums%20%3D%20%5Bi%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%E8%A6%81%E7%B4%A0%E3%82%92%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E3%81%AB%E3%82%B7%E3%83%A3%E3%83%83%E3%83%95%E3%83%AB%0A%20%20%20%20random.shuffle%28nums%29%0A%20%20%20%20return%20nums%0A%0Adef%20find_one%28nums%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%85%8D%E5%88%97%20nums%20%E5%86%85%E3%81%A7%E6%95%B0%E5%80%A4%201%20%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E6%8E%A2%E3%81%99%22%22%22%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%201%20%E3%81%8C%E9%85%8D%E5%88%97%E3%81%AE%E5%85%88%E9%A0%AD%E3%81%AB%E3%81%82%E3%82%8B%E3%81%A8%E3%81%8D%E3%80%81%E6%9C%80%E8%89%AF%E6%99%82%E9%96%93%E8%A8%88%E7%AE%97%E9%87%8F%20O%281%29%20%E3%81%A8%E3%81%AA%E3%82%8B%0A%20%20%20%20%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%201%20%E3%81%8C%E9%85%8D%E5%88%97%E3%81%AE%E6%9C%AB%E5%B0%BE%E3%81%AB%E3%81%82%E3%82%8B%E3%81%A8%E3%81%8D%E3%80%81%E6%9C%80%E6%82%AA%E6%99%82%E9%96%93%E8%A8%88%E7%AE%97%E9%87%8F%20O%28n%29%20%E3%81%A8%E3%81%AA%E3%82%8B%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20return%20-1%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%2010%0A%20%20%20%20nums%20%3D%20random_numbers%28n%29%0A%20%20%20%20index%20%3D%20find_one%28nums%29%0A%20%20%20%20print%28%22%5Cn%E9%85%8D%E5%88%97%20%5B%201%2C%202%2C%20...%2C%20n%20%5D%20%E3%82%92%E3%82%B7%E3%83%A3%E3%83%83%E3%83%95%E3%83%AB%E3%81%99%E3%82%8B%E3%81%A8%20%3D%22%2C%20nums%29%0A%20%20%20%20print%28%22%E6%95%B0%E5%80%A4%201%20%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AF%22%2C%20index%29&cumulative=false&curInstr=25&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_divide_and_conquer/binary_search_recur.md b/ja/codes/pythontutor/chapter_divide_and_conquer/binary_search_recur.md new file mode 100644 index 000000000..1c7172d44 --- /dev/null +++ b/ja/codes/pythontutor/chapter_divide_and_conquer/binary_search_recur.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20dfs%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%2C%20i%3A%20int%2C%20j%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%EF%BC%9A%E5%95%8F%E9%A1%8C%20f%28i%2C%20j%29%22%22%22%0A%20%20%20%20%23%20%E5%8C%BA%E9%96%93%E3%81%8C%E7%A9%BA%E3%81%AA%E3%82%89%E5%AF%BE%E8%B1%A1%E8%A6%81%E7%B4%A0%E3%81%AF%E5%AD%98%E5%9C%A8%E3%81%97%E3%81%AA%E3%81%84%E3%81%AE%E3%81%A7%20-1%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3E%20j%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20%E4%B8%AD%E7%82%B9%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20m%20%E3%82%92%E8%A8%88%E7%AE%97%0A%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%0A%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%20f%28m%2B1%2C%20j%29%20%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E8%A7%A3%E3%81%8F%0A%20%20%20%20%20%20%20%20return%20dfs%28nums%2C%20target%2C%20m%20%2B%201%2C%20j%29%0A%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%20f%28i%2C%20m-1%29%20%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E8%A7%A3%E3%81%8F%0A%20%20%20%20%20%20%20%20return%20dfs%28nums%2C%20target%2C%20i%2C%20m%20-%201%29%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A8%99%E8%A6%81%E7%B4%A0%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E3%82%89%E3%81%9D%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20%20%20%20%20return%20m%0A%0Adef%20binary_search%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%95%8F%E9%A1%8C%20f%280%2C%20n-1%29%20%E3%82%92%E8%A7%A3%E3%81%8F%0A%20%20%20%20return%20dfs%28nums%2C%20target%2C%200%2C%20n%20-%201%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%EF%BC%88%E4%B8%A1%E9%96%89%E5%8C%BA%E9%96%93%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search%28nums%2C%20target%29%0A%20%20%20%20print%28%22%E5%AF%BE%E8%B1%A1%E8%A6%81%E7%B4%A0%206%20%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20%3D%20%22%2C%20index%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_divide_and_conquer/build_tree.md b/ja/codes/pythontutor/chapter_divide_and_conquer/build_tree.md new file mode 100644 index 000000000..04b4c6b29 --- /dev/null +++ b/ja/codes/pythontutor/chapter_divide_and_conquer/build_tree.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%20%3D%200%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20dfs%28%0A%20%20%20%20preorder%3A%20list%5Bint%5D%2C%0A%20%20%20%20inorder_map%3A%20dict%5Bint%2C%20int%5D%2C%0A%20%20%20%20i%3A%20int%2C%0A%20%20%20%20l%3A%20int%2C%0A%20%20%20%20r%3A%20int%2C%0A%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%82%92%E6%A7%8B%E7%AF%89%EF%BC%9A%E5%88%86%E5%89%B2%E7%B5%B1%E6%B2%BB%22%22%22%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E6%9C%A8%E5%8C%BA%E9%96%93%E3%81%8C%E7%A9%BA%E3%81%AA%E3%82%89%E7%B5%82%E4%BA%86%E3%81%99%E3%82%8B%0A%20%20%20%20if%20r%20-%20l%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%88%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20root%20%3D%20TreeNode%28preorder%5Bi%5D%29%0A%20%20%20%20%23%20m%20%E3%82%92%E6%B1%82%E3%82%81%E3%81%A6%E5%B7%A6%E5%8F%B3%E9%83%A8%E5%88%86%E6%9C%A8%E3%82%92%E5%88%86%E5%89%B2%E3%81%99%E3%82%8B%0A%20%20%20%20m%20%3D%20inorder_map%5Bpreorder%5Bi%5D%5D%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%EF%BC%9A%E5%B7%A6%E9%83%A8%E5%88%86%E6%9C%A8%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root.left%20%3D%20dfs%28preorder%2C%20inorder_map%2C%20i%20%2B%201%2C%20l%2C%20m%20-%201%29%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%EF%BC%9A%E5%8F%B3%E9%83%A8%E5%88%86%E6%9C%A8%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root.right%20%3D%20dfs%28preorder%2C%20inorder_map%2C%20i%20%2B%201%20%2B%20m%20-%20l%2C%20m%20%2B%201%2C%20r%29%0A%20%20%20%20%23%20%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20root%0A%0A%0Adef%20build_tree%28preorder%3A%20list%5Bint%5D%2C%20inorder%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%82%92%E6%A7%8B%E7%AF%89%22%22%22%0A%20%20%20%20%23%20inorder%20%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%8B%E3%82%89%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%B8%E3%81%AE%E5%AF%BE%E5%BF%9C%E3%82%92%E6%A0%BC%E7%B4%8D%E3%81%99%E3%82%8B%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20inorder_map%20%3D%20%7Bval%3A%20i%20for%20i%2C%20val%20in%20enumerate%28inorder%29%7D%0A%20%20%20%20root%20%3D%20dfs%28preorder%2C%20inorder_map%2C%200%2C%200%2C%20len%28inorder%29%20-%201%29%0A%20%20%20%20return%20root%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20preorder%20%3D%20%5B3%2C%209%2C%202%2C%201%2C%207%5D%0A%20%20%20%20inorder%20%3D%20%5B9%2C%203%2C%201%2C%202%2C%207%5D%0A%20%20%20%20print%28f%22%E5%89%8D%E9%A0%86%E8%B5%B0%E6%9F%BB%20%3D%20%7Bpreorder%7D%22%29%0A%20%20%20%20print%28f%22%E4%B8%AD%E9%A0%86%E8%B5%B0%E6%9F%BB%20%3D%20%7Binorder%7D%22%29%0A%20%20%20%20root%20%3D%20build_tree%28preorder%2C%20inorder%29&cumulative=false&curInstr=21&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_divide_and_conquer/hanota.md b/ja/codes/pythontutor/chapter_divide_and_conquer/hanota.md new file mode 100644 index 000000000..347726066 --- /dev/null +++ b/ja/codes/pythontutor/chapter_divide_and_conquer/hanota.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20move%28src%3A%20list%5Bint%5D%2C%20tar%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%86%86%E7%9B%A4%E3%82%92%201%20%E6%9E%9A%E7%A7%BB%E5%8B%95%22%22%22%0A%20%20%20%20%23%20src%20%E3%81%AE%E4%B8%8A%E3%81%8B%E3%82%89%E5%86%86%E7%9B%A4%E3%82%921%E6%9E%9A%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%99%0A%20%20%20%20pan%20%3D%20src.pop%28%29%0A%20%20%20%20%23%20%E5%86%86%E7%9B%A4%E3%82%92%20tar%20%E3%81%AE%E4%B8%8A%E3%81%AB%E7%BD%AE%E3%81%8F%0A%20%20%20%20tar.append%28pan%29%0A%0A%0Adef%20dfs%28i%3A%20int%2C%20src%3A%20list%5Bint%5D%2C%20buf%3A%20list%5Bint%5D%2C%20tar%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E3%83%8F%E3%83%8E%E3%82%A4%E3%81%AE%E5%A1%94%E3%81%AE%E5%95%8F%E9%A1%8C%20f%28i%29%20%E3%82%92%E8%A7%A3%E3%81%8F%22%22%22%0A%20%20%20%20%23%20src%20%E3%81%AB%E5%86%86%E7%9B%A4%E3%81%8C%201%20%E6%9E%9A%E3%81%A0%E3%81%91%E6%AE%8B%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AF%E3%80%81%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%20tar%20%E3%81%B8%E7%A7%BB%E3%81%99%0A%20%20%20%20if%20i%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20move%28src%2C%20tar%29%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%20f%28i-1%29%EF%BC%9Asrc%20%E3%81%AE%E4%B8%8A%E9%83%A8%20i-1%20%E6%9E%9A%E3%81%AE%E5%86%86%E7%9B%A4%E3%82%92%20tar%20%E3%82%92%E8%A3%9C%E5%8A%A9%E3%81%AB%E3%81%97%E3%81%A6%20buf%20%E3%81%B8%E7%A7%BB%E3%81%99%0A%20%20%20%20dfs%28i%20-%201%2C%20src%2C%20tar%2C%20buf%29%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%20f%281%29%EF%BC%9Asrc%20%E3%81%AB%E6%AE%8B%E3%82%8B%201%20%E6%9E%9A%E3%81%AE%E5%86%86%E7%9B%A4%E3%82%92%20tar%20%E3%81%AB%E7%A7%BB%E3%81%99%0A%20%20%20%20move%28src%2C%20tar%29%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%20f%28i-1%29%EF%BC%9Abuf%20%E3%81%AE%E4%B8%8A%E9%83%A8%20i-1%20%E6%9E%9A%E3%81%AE%E5%86%86%E7%9B%A4%E3%82%92%20src%20%E3%82%92%E8%A3%9C%E5%8A%A9%E3%81%AB%E3%81%97%E3%81%A6%20tar%20%E3%81%B8%E7%A7%BB%E3%81%99%0A%20%20%20%20dfs%28i%20-%201%2C%20buf%2C%20src%2C%20tar%29%0A%0A%0Adef%20solve_hanota%28A%3A%20list%5Bint%5D%2C%20B%3A%20list%5Bint%5D%2C%20C%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E3%83%8F%E3%83%8E%E3%82%A4%E3%81%AE%E5%A1%94%E3%82%92%E8%A7%A3%E3%81%8F%22%22%22%0A%20%20%20%20n%20%3D%20len%28A%29%0A%20%20%20%20%23%20A%20%E3%81%AE%E4%B8%8A%E3%81%8B%E3%82%89%20n%20%E6%9E%9A%E3%81%AE%E5%86%86%E7%9B%A4%E3%82%92%20B%20%E3%82%92%E4%BB%8B%E3%81%97%E3%81%A6%20C%20%E3%81%B8%E7%A7%BB%E3%81%99%0A%20%20%20%20dfs%28n%2C%20A%2C%20B%2C%20C%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E3%83%AA%E3%82%B9%E3%83%88%E6%9C%AB%E5%B0%BE%E3%81%8C%E6%9F%B1%E3%81%AE%E9%A0%82%E4%B8%8A%0A%20%20%20%20A%20%3D%20%5B5%2C%204%2C%203%2C%202%2C%201%5D%0A%20%20%20%20B%20%3D%20%5B%5D%0A%20%20%20%20C%20%3D%20%5B%5D%0A%20%20%20%20print%28%22%E5%88%9D%E6%9C%9F%E7%8A%B6%E6%85%8B%3A%22%29%0A%20%20%20%20print%28f%22A%20%3D%20%7BA%7D%22%29%0A%20%20%20%20print%28f%22B%20%3D%20%7BB%7D%22%29%0A%20%20%20%20print%28f%22C%20%3D%20%7BC%7D%22%29%0A%0A%20%20%20%20solve_hanota%28A%2C%20B%2C%20C%29%0A%0A%20%20%20%20print%28%22%E5%86%86%E7%9B%A4%E3%81%AE%E7%A7%BB%E5%8B%95%E5%AE%8C%E4%BA%86%E5%BE%8C%3A%22%29%0A%20%20%20%20print%28f%22A%20%3D%20%7BA%7D%22%29%0A%20%20%20%20print%28f%22B%20%3D%20%7BB%7D%22%29%0A%20%20%20%20print%28f%22C%20%3D%20%7BC%7D%22%29&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_backtrack.md b/ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_backtrack.md new file mode 100644 index 000000000..23a68b284 --- /dev/null +++ b/ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_backtrack.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20backtrack%28choices%3A%20list%5Bint%5D%2C%20state%3A%20int%2C%20n%3A%20int%2C%20res%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0%22%22%22%0A%20%20%20%20%23%20%E7%AC%AC%20n%20%E6%AE%B5%E3%81%AB%E5%88%B0%E9%81%94%E3%81%97%E3%81%9F%E3%82%89%E3%80%81%E6%96%B9%E6%B3%95%E6%95%B0%E3%82%92%201%20%E5%A2%97%E3%82%84%E3%81%99%0A%20%20%20%20if%20state%20%3D%3D%20n%3A%0A%20%20%20%20%20%20%20%20res%5B0%5D%20%2B%3D%201%0A%20%20%20%20%23%20%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%81%B8%E6%8A%9E%E8%82%A2%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20for%20choice%20in%20choices%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9E%9D%E5%88%88%E3%82%8A%3A%20%E7%AC%AC%20n%20%E6%AE%B5%E3%82%92%E8%B6%85%E3%81%88%E3%81%AA%E3%81%84%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20if%20state%20%2B%20choice%20%3E%20n%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%23%20%E8%A9%A6%E8%A1%8C%3A%20%E9%81%B8%E6%8A%9E%E3%82%92%E8%A1%8C%E3%81%84%E3%80%81%E7%8A%B6%E6%85%8B%E3%82%92%E6%9B%B4%E6%96%B0%0A%20%20%20%20%20%20%20%20backtrack%28choices%2C%20state%20%2B%20choice%2C%20n%2C%20res%29%0A%20%20%20%20%20%20%20%20%23%20%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AF%0A%0A%0Adef%20climbing_stairs_backtrack%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8E%E6%AE%B5%E7%99%BB%E3%82%8A%EF%BC%9A%E3%83%90%E3%83%83%E3%82%AF%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0%22%22%22%0A%20%20%20%20choices%20%3D%20%5B1%2C%202%5D%20%20%23%201%20%E6%AE%B5%E3%81%BE%E3%81%9F%E3%81%AF%202%20%E6%AE%B5%E4%B8%8A%E3%82%8B%E3%81%93%E3%81%A8%E3%82%92%E9%81%B8%E3%81%B9%E3%82%8B%0A%20%20%20%20state%20%3D%200%20%20%23%20%E7%AC%AC%200%20%E6%AE%B5%E3%81%8B%E3%82%89%E4%B8%8A%E3%82%8A%E5%A7%8B%E3%82%81%E3%82%8B%0A%20%20%20%20res%20%3D%20%5B0%5D%20%20%23%20res%5B0%5D%20%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E6%96%B9%E6%B3%95%E6%95%B0%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%99%E3%82%8B%0A%20%20%20%20backtrack%28choices%2C%20state%2C%20n%2C%20res%29%0A%20%20%20%20return%20res%5B0%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%204%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_backtrack%28n%29%0A%20%20%20%20print%28f%22%7Bn%7D%20%E6%AE%B5%E3%81%AE%E9%9A%8E%E6%AE%B5%E3%82%92%E4%B8%8A%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%AF%E5%85%A8%E9%83%A8%E3%81%A7%20%7Bres%7D%20%E9%80%9A%E3%82%8A%E3%81%A7%E3%81%99%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_constraint_dp.md b/ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_constraint_dp.md new file mode 100644 index 000000000..5d731d5df --- /dev/null +++ b/ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_constraint_dp.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20climbing_stairs_constraint_dp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%88%B6%E7%B4%84%E4%BB%98%E3%81%8D%E9%9A%8E%E6%AE%B5%E7%99%BB%E3%82%8A%EF%BC%9A%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%201%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%AE%E8%A7%A3%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AB%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%203%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E5%88%9D%E6%9C%9F%E7%8A%B6%E6%85%8B%EF%BC%9A%E6%9C%80%E5%B0%8F%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%AE%E8%A7%A3%E3%82%92%E3%81%82%E3%82%89%E3%81%8B%E3%81%98%E3%82%81%E8%A8%AD%E5%AE%9A%0A%20%20%20%20dp%5B1%5D%5B1%5D%2C%20dp%5B1%5D%5B2%5D%20%3D%201%2C%200%0A%20%20%20%20dp%5B2%5D%5B1%5D%2C%20dp%5B2%5D%5B2%5D%20%3D%200%2C%201%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%B0%8F%E3%81%95%E3%81%84%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%8B%E3%82%89%E5%A4%A7%E3%81%8D%E3%81%84%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%B8%E9%A0%86%E3%81%AB%E8%A7%A3%E3%81%8F%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B1%5D%20%3D%20dp%5Bi%20-%201%5D%5B2%5D%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B2%5D%20%3D%20dp%5Bi%20-%202%5D%5B1%5D%20%2B%20dp%5Bi%20-%202%5D%5B2%5D%0A%20%20%20%20return%20dp%5Bn%5D%5B1%5D%20%2B%20dp%5Bn%5D%5B2%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_constraint_dp%28n%29%0A%20%20%20%20print%28f%22%7Bn%7D%20%E6%AE%B5%E3%81%AE%E9%9A%8E%E6%AE%B5%E3%82%92%E4%B8%8A%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%AF%E5%85%A8%E9%83%A8%E3%81%A7%20%7Bres%7D%20%E9%80%9A%E3%82%8A%E3%81%A7%E3%81%99%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs.md b/ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs.md new file mode 100644 index 000000000..a85d3bb3b --- /dev/null +++ b/ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20dfs%28i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%A4%9C%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20dp%5B1%5D%20%E3%81%A8%20dp%5B2%5D%20%E3%81%AF%E6%97%A2%E7%9F%A5%E3%81%AA%E3%81%AE%E3%81%A7%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3D%3D%201%20or%20i%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20%23%20dp%5Bi%5D%20%3D%20dp%5Bi-1%5D%20%2B%20dp%5Bi-2%5D%0A%20%20%20%20count%20%3D%20dfs%28i%20-%201%29%20%2B%20dfs%28i%20-%202%29%0A%20%20%20%20return%20count%0A%0A%0Adef%20climbing_stairs_dfs%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8E%E6%AE%B5%E7%99%BB%E3%82%8A%EF%BC%9A%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20return%20dfs%28n%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dfs%28n%29%0A%20%20%20%20print%28f%22%7Bn%7D%20%E6%AE%B5%E3%81%AE%E9%9A%8E%E6%AE%B5%E3%82%92%E4%B8%8A%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%AF%E5%85%A8%E9%83%A8%E3%81%A7%20%7Bres%7D%20%E9%80%9A%E3%82%8A%E3%81%A7%E3%81%99%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs_mem.md b/ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs_mem.md new file mode 100644 index 000000000..639138ddc --- /dev/null +++ b/ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dfs_mem.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20dfs%28i%3A%20int%2C%20mem%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E3%83%A1%E3%83%A2%E5%8C%96%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20dp%5B1%5D%20%E3%81%A8%20dp%5B2%5D%20%E3%81%AF%E6%97%A2%E7%9F%A5%E3%81%AA%E3%81%AE%E3%81%A7%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3D%3D%201%20or%20i%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20i%0A%20%20%20%20%23%20dp%5Bi%5D%20%E3%81%AE%E8%A8%98%E9%8C%B2%E3%81%8C%E3%81%82%E3%82%8C%E3%81%B0%E3%80%81%E3%81%9D%E3%82%8C%E3%82%92%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E8%BF%94%E3%81%99%0A%20%20%20%20if%20mem%5Bi%5D%20%21%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%0A%20%20%20%20%23%20dp%5Bi%5D%20%3D%20dp%5Bi-1%5D%20%2B%20dp%5Bi-2%5D%0A%20%20%20%20count%20%3D%20dfs%28i%20-%201%2C%20mem%29%20%2B%20dfs%28i%20-%202%2C%20mem%29%0A%20%20%20%20%23%20dp%5Bi%5D%20%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%99%E3%82%8B%0A%20%20%20%20mem%5Bi%5D%20%3D%20count%0A%20%20%20%20return%20count%0A%0A%0Adef%20climbing_stairs_dfs_mem%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8E%E6%AE%B5%E7%99%BB%E3%82%8A%EF%BC%9A%E3%83%A1%E3%83%A2%E5%8C%96%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20mem%5Bi%5D%20%E3%81%AF%E7%AC%AC%20i%20%E6%AE%B5%E3%81%BE%E3%81%A7%E4%B8%8A%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%AE%E7%B7%8F%E6%95%B0%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%97%E3%80%81-1%20%E3%81%AF%E6%9C%AA%E8%A8%98%E9%8C%B2%E3%82%92%E8%A1%A8%E3%81%99%0A%20%20%20%20mem%20%3D%20%5B-1%5D%20%2A%20%28n%20%2B%201%29%0A%20%20%20%20return%20dfs%28n%2C%20mem%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dfs_mem%28n%29%0A%20%20%20%20print%28f%22%7Bn%7D%20%E6%AE%B5%E3%81%AE%E9%9A%8E%E6%AE%B5%E3%82%92%E4%B8%8A%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%AF%E5%85%A8%E9%83%A8%E3%81%A7%20%7Bres%7D%20%E9%80%9A%E3%82%8A%E3%81%A7%E3%81%99%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dp.md b/ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dp.md new file mode 100644 index 000000000..9aa23d1ec --- /dev/null +++ b/ja/codes/pythontutor/chapter_dynamic_programming/climbing_stairs_dp.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20climbing_stairs_dp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8E%E6%AE%B5%E7%99%BB%E3%82%8A%EF%BC%9A%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%AE%E8%A7%A3%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AB%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28n%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E6%9C%9F%E7%8A%B6%E6%85%8B%EF%BC%9A%E6%9C%80%E5%B0%8F%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%AE%E8%A7%A3%E3%82%92%E3%81%82%E3%82%89%E3%81%8B%E3%81%98%E3%82%81%E8%A8%AD%E5%AE%9A%0A%20%20%20%20dp%5B1%5D%2C%20dp%5B2%5D%20%3D%201%2C%202%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%B0%8F%E3%81%95%E3%81%84%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%8B%E3%82%89%E5%A4%A7%E3%81%8D%E3%81%84%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%B8%E9%A0%86%E3%81%AB%E8%A7%A3%E3%81%8F%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%20%3D%20dp%5Bi%20-%201%5D%20%2B%20dp%5Bi%20-%202%5D%0A%20%20%20%20return%20dp%5Bn%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dp%28n%29%0A%20%20%20%20print%28f%22%7Bn%7D%20%E6%AE%B5%E3%81%AE%E9%9A%8E%E6%AE%B5%E3%82%92%E4%B8%8A%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%AF%E5%85%A8%E9%83%A8%E3%81%A7%20%7Bres%7D%20%E9%80%9A%E3%82%8A%E3%81%A7%E3%81%99%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20climbing_stairs_dp_comp%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8E%E6%AE%B5%E7%99%BB%E3%82%8A%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E3%81%97%E3%81%9F%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20n%0A%20%20%20%20a%2C%20b%20%3D%201%2C%202%0A%20%20%20%20for%20_%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20a%2C%20b%20%3D%20b%2C%20a%20%2B%20b%0A%20%20%20%20return%20b%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%209%0A%0A%20%20%20%20res%20%3D%20climbing_stairs_dp_comp%28n%29%0A%20%20%20%20print%28f%22%7Bn%7D%20%E6%AE%B5%E3%81%AE%E9%9A%8E%E6%AE%B5%E3%82%92%E4%B8%8A%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%AF%E5%85%A8%E9%83%A8%E3%81%A7%20%7Bres%7D%20%E9%80%9A%E3%82%8A%E3%81%A7%E3%81%99%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_dynamic_programming/coin_change.md b/ja/codes/pythontutor/chapter_dynamic_programming/coin_change.md new file mode 100644 index 000000000..67f8a96f8 --- /dev/null +++ b/ja/codes/pythontutor/chapter_dynamic_programming/coin_change.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20coin_change_dp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E3%82%B3%E3%82%A4%E3%83%B3%E4%B8%A1%E6%9B%BF%EF%BC%9A%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20MAX%20%3D%20amt%20%2B%201%0A%20%20%20%20%23%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28amt%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%85%88%E9%A0%AD%E8%A1%8C%E3%81%A8%E5%85%88%E9%A0%AD%E5%88%97%0A%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Ba%5D%20%3D%20MAX%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%3A%20%E6%AE%8B%E3%82%8A%E3%81%AE%E8%A1%8C%E3%81%A8%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%AA%E3%82%89%E7%A1%AC%E8%B2%A8%20i%20%E3%81%AF%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%A1%AC%E8%B2%A8%20i%20%E3%82%92%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E9%81%B8%E3%81%B6%E5%A0%B4%E5%90%88%E3%81%AE%E5%B0%8F%E3%81%95%E3%81%84%E6%96%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20min%28dp%5Bi%20-%201%5D%5Ba%5D%2C%20dp%5Bi%5D%5Ba%20-%20coins%5Bi%20-%201%5D%5D%20%2B%201%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bamt%5D%20if%20dp%5Bn%5D%5Bamt%5D%20%21%3D%20MAX%20else%20-1%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%204%0A%0A%20%20%20%20%23%20%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20coin_change_dp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E3%82%92%E4%BD%9C%E3%82%8B%E3%81%AE%E3%81%AB%E5%BF%85%E8%A6%81%E3%81%AA%E6%9C%80%E5%B0%8F%E7%A1%AC%E8%B2%A8%E6%9E%9A%E6%95%B0%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20coin_change_dp_comp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E3%82%B3%E3%82%A4%E3%83%B3%E4%BA%A4%E6%8F%9B%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20MAX%20%3D%20amt%20%2B%201%0A%20%20%20%20%23%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5BMAX%5D%20%2A%20%28amt%20%2B%201%29%0A%20%20%20%20dp%5B0%5D%20%3D%200%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%A0%86%E6%96%B9%E5%90%91%E3%81%AB%E8%B5%B0%E6%9F%BB%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%AA%E3%82%89%E7%A1%AC%E8%B2%A8%20i%20%E3%81%AF%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%A1%AC%E8%B2%A8%20i%20%E3%82%92%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E9%81%B8%E3%81%B6%E5%A0%B4%E5%90%88%E3%81%AE%E5%B0%8F%E3%81%95%E3%81%84%E6%96%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20min%28dp%5Ba%5D%2C%20dp%5Ba%20-%20coins%5Bi%20-%201%5D%5D%20%2B%201%29%0A%20%20%20%20return%20dp%5Bamt%5D%20if%20dp%5Bamt%5D%20%21%3D%20MAX%20else%20-1%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%204%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20coin_change_dp_comp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E3%82%92%E4%BD%9C%E3%82%8B%E3%81%AE%E3%81%AB%E5%BF%85%E8%A6%81%E3%81%AA%E6%9C%80%E5%B0%8F%E7%A1%AC%E8%B2%A8%E6%9E%9A%E6%95%B0%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_dynamic_programming/coin_change_ii.md b/ja/codes/pythontutor/chapter_dynamic_programming/coin_change_ii.md new file mode 100644 index 000000000..840b5a4fd --- /dev/null +++ b/ja/codes/pythontutor/chapter_dynamic_programming/coin_change_ii.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20coin_change_ii_dp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E3%82%B3%E3%82%A4%E3%83%B3%E4%B8%A1%E6%9B%BF%20II%EF%BC%9A%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20%23%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28amt%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E5%85%88%E9%A0%AD%E5%88%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%201%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%AA%E3%82%89%E7%A1%AC%E8%B2%A8%20i%20%E3%81%AF%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%82%B3%E3%82%A4%E3%83%B3%20i%20%E3%82%92%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E9%81%B8%E3%81%B6%E5%A0%B4%E5%90%88%E3%81%AE%E5%92%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Ba%5D%20%3D%20dp%5Bi%20-%201%5D%5Ba%5D%20%2B%20dp%5Bi%5D%5Ba%20-%20coins%5Bi%20-%201%5D%5D%0A%20%20%20%20return%20dp%5Bn%5D%5Bamt%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%205%0A%0A%20%20%20%20%23%20%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20coin_change_ii_dp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E3%82%92%E4%BD%9C%E3%82%8B%E7%A1%AC%E8%B2%A8%E3%81%AE%E7%B5%84%E3%81%BF%E5%90%88%E3%82%8F%E3%81%9B%E6%95%B0%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20coin_change_ii_dp_comp%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E3%82%B3%E3%82%A4%E3%83%B3%E4%B8%A1%E6%9B%BF%20II%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E3%81%97%E3%81%9F%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%20%3D%20len%28coins%29%0A%20%20%20%20%23%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28amt%20%2B%201%29%0A%20%20%20%20dp%5B0%5D%20%3D%201%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%A0%86%E6%96%B9%E5%90%91%E3%81%AB%E8%B5%B0%E6%9F%BB%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20for%20a%20in%20range%281%2C%20amt%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20coins%5Bi%20-%201%5D%20%3E%20a%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%AA%E3%82%89%E7%A1%AC%E8%B2%A8%20i%20%E3%81%AF%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%82%B3%E3%82%A4%E3%83%B3%20i%20%E3%82%92%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E9%81%B8%E3%81%B6%E5%A0%B4%E5%90%88%E3%81%AE%E5%92%8C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Ba%5D%20%3D%20dp%5Ba%5D%20%2B%20dp%5Ba%20-%20coins%5Bi%20-%201%5D%5D%0A%20%20%20%20return%20dp%5Bamt%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20coins%20%3D%20%5B1%2C%202%2C%205%5D%0A%20%20%20%20amt%20%3D%205%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20coin_change_ii_dp_comp%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%E7%9B%AE%E6%A8%99%E9%87%91%E9%A1%8D%E3%82%92%E4%BD%9C%E3%82%8B%E7%A1%AC%E8%B2%A8%E3%81%AE%E7%B5%84%E3%81%BF%E5%90%88%E3%82%8F%E3%81%9B%E6%95%B0%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_dynamic_programming/edit_distance.md b/ja/codes/pythontutor/chapter_dynamic_programming/edit_distance.md new file mode 100644 index 000000000..3b8d5efca --- /dev/null +++ b/ja/codes/pythontutor/chapter_dynamic_programming/edit_distance.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20edit_distance_dp%28s%3A%20str%2C%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%B7%A8%E9%9B%86%E8%B7%9D%E9%9B%A2%EF%BC%9A%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28m%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%85%88%E9%A0%AD%E8%A1%8C%E3%81%A8%E5%85%88%E9%A0%AD%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20i%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%3A%20%E6%AE%8B%E3%82%8A%E3%81%AE%E8%A1%8C%E3%81%A8%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%202%20%E3%81%A4%E3%81%AE%E6%96%87%E5%AD%97%E3%81%8C%E7%AD%89%E3%81%97%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E3%81%9D%E3%81%AE%202%20%E6%96%87%E5%AD%97%E3%82%92%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E3%82%B9%E3%82%AD%E3%83%83%E3%83%97%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%B0%8F%E7%B7%A8%E9%9B%86%E5%9B%9E%E6%95%B0%20%3D%20%E6%8C%BF%E5%85%A5%E3%83%BB%E5%89%8A%E9%99%A4%E3%83%BB%E7%BD%AE%E6%8F%9B%E3%81%AE%203%20%E6%93%8D%E4%BD%9C%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E6%9C%80%E5%B0%8F%E7%B7%A8%E9%9B%86%E5%9B%9E%E6%95%B0%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D%2C%20dp%5Bi%20-%201%5D%5Bj%5D%2C%20dp%5Bi%20-%201%5D%5Bj%20-%201%5D%29%20%2B%201%0A%20%20%20%20return%20dp%5Bn%5D%5Bm%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%0A%20%20%20%20%23%20%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20edit_distance_dp%28s%2C%20t%29%0A%20%20%20%20print%28f%22%7Bs%7D%20%E3%82%92%20%7Bt%7D%20%E3%81%AB%E5%A4%89%E6%9B%B4%E3%81%99%E3%82%8B%E3%81%AB%E3%81%AF%E6%9C%80%E5%B0%8F%E3%81%A7%20%7Bres%7D%20%E5%9B%9E%E3%81%AE%E7%B7%A8%E9%9B%86%E3%81%8C%E5%BF%85%E8%A6%81%E3%81%A7%E3%81%99%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20edit_distance_dp_comp%28s%3A%20str%2C%20t%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%B7%A8%E9%9B%86%E8%B7%9D%E9%9B%A2%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E3%81%97%E3%81%9F%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28m%20%2B%201%29%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%85%88%E9%A0%AD%E8%A1%8C%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20j%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E6%AE%8B%E3%82%8A%E3%81%AE%E8%A1%8C%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%85%88%E9%A0%AD%E5%88%97%0A%20%20%20%20%20%20%20%20leftup%20%3D%20dp%5B0%5D%20%20%23%20dp%5Bi-1%2C%20j-1%5D%20%E3%82%92%E4%B8%80%E6%99%82%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20dp%5B0%5D%20%2B%3D%201%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E6%AE%8B%E3%82%8A%E3%81%AE%E5%88%97%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20temp%20%3D%20dp%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20s%5Bi%20-%201%5D%20%3D%3D%20t%5Bj%20-%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%202%20%E3%81%A4%E3%81%AE%E6%96%87%E5%AD%97%E3%81%8C%E7%AD%89%E3%81%97%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E3%81%9D%E3%81%AE%202%20%E6%96%87%E5%AD%97%E3%82%92%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E3%82%B9%E3%82%AD%E3%83%83%E3%83%97%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20leftup%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%B0%8F%E7%B7%A8%E9%9B%86%E5%9B%9E%E6%95%B0%20%3D%20%E6%8C%BF%E5%85%A5%E3%83%BB%E5%89%8A%E9%99%A4%E3%83%BB%E7%BD%AE%E6%8F%9B%E3%81%AE%203%20%E6%93%8D%E4%BD%9C%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E6%9C%80%E5%B0%8F%E7%B7%A8%E9%9B%86%E5%9B%9E%E6%95%B0%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20min%28dp%5Bj%20-%201%5D%2C%20dp%5Bj%5D%2C%20leftup%29%20%2B%201%0A%20%20%20%20%20%20%20%20%20%20%20%20leftup%20%3D%20temp%20%20%23%20%E6%AC%A1%E3%81%AE%E5%8F%8D%E5%BE%A9%E3%81%AE%20dp%5Bi-1%2C%20j-1%5D%20%E3%81%AB%E6%9B%B4%E6%96%B0%E3%81%99%E3%82%8B%0A%20%20%20%20return%20dp%5Bm%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20s%20%3D%20%22bag%22%0A%20%20%20%20t%20%3D%20%22pack%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28s%29%2C%20len%28t%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20edit_distance_dp_comp%28s%2C%20t%29%0A%20%20%20%20print%28f%22%7Bs%7D%20%E3%82%92%20%7Bt%7D%20%E3%81%AB%E5%A4%89%E6%9B%B4%E3%81%99%E3%82%8B%E3%81%AB%E3%81%AF%E6%9C%80%E5%B0%8F%E3%81%A7%20%7Bres%7D%20%E5%9B%9E%E3%81%AE%E7%B7%A8%E9%9B%86%E3%81%8C%E5%BF%85%E8%A6%81%E3%81%A7%E3%81%99%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_dynamic_programming/knapsack.md b/ja/codes/pythontutor/chapter_dynamic_programming/knapsack.md new file mode 100644 index 000000000..ba91f608d --- /dev/null +++ b/ja/codes/pythontutor/chapter_dynamic_programming/knapsack.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=def%20knapsack_dfs%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20i%3A%20int%2C%20c%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%EF%BC%9A%E7%B7%8F%E5%BD%93%E3%81%9F%E3%82%8A%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E5%93%81%E7%89%A9%E3%82%92%E9%81%B8%E3%81%B3%E7%B5%82%E3%81%88%E3%81%9F%E3%81%8B%E3%80%81%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E3%81%AB%E6%AE%8B%E3%82%8A%E5%AE%B9%E9%87%8F%E3%81%8C%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E4%BE%A1%E5%80%A4%200%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3D%3D%200%20or%20c%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AF%E3%80%81%E5%85%A5%E3%82%8C%E3%81%AA%E3%81%84%E9%81%B8%E6%8A%9E%E3%81%97%E3%81%8B%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84%0A%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20return%20knapsack_dfs%28wgt%2C%20val%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20%23%20%E5%93%81%E7%89%A9%20i%20%E3%82%92%E5%85%A5%E3%82%8C%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E5%85%A5%E3%82%8C%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AE%E6%9C%80%E5%A4%A7%E4%BE%A1%E5%80%A4%E3%82%92%E8%A8%88%E7%AE%97%E3%81%99%E3%82%8B%0A%20%20%20%20no%20%3D%20knapsack_dfs%28wgt%2C%20val%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20yes%20%3D%20knapsack_dfs%28wgt%2C%20val%2C%20i%20-%201%2C%20c%20-%20wgt%5Bi%20-%201%5D%29%20%2B%20val%5Bi%20-%201%5D%0A%20%20%20%20%23%202%E3%81%A4%E3%81%AE%E6%A1%88%E3%81%AE%E3%81%86%E3%81%A1%E4%BE%A1%E5%80%A4%E3%81%8C%E5%A4%A7%E3%81%8D%E3%81%84%E3%81%BB%E3%81%86%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20max%28no%2C%20yes%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E5%85%A8%E6%8E%A2%E7%B4%A2%0A%20%20%20%20res%20%3D%20knapsack_dfs%28wgt%2C%20val%2C%20n%2C%20cap%29%0A%20%20%20%20print%28f%22%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%81%AA%E3%81%84%E6%9C%80%E5%A4%A7%E4%BE%A1%E5%80%A4%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20knapsack_dfs_mem%28%0A%20%20%20%20wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20mem%3A%20list%5Blist%5Bint%5D%5D%2C%20i%3A%20int%2C%20c%3A%20int%0A%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%EF%BC%9A%E3%83%A1%E3%83%A2%E5%8C%96%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E5%93%81%E7%89%A9%E3%82%92%E9%81%B8%E3%81%B3%E7%B5%82%E3%81%88%E3%81%9F%E3%81%8B%E3%80%81%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E3%81%AB%E6%AE%8B%E3%82%8A%E5%AE%B9%E9%87%8F%E3%81%8C%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E4%BE%A1%E5%80%A4%200%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3D%3D%200%20or%20c%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%200%0A%20%20%20%20%23%20%E6%97%A2%E3%81%AB%E8%A8%98%E9%8C%B2%E3%81%8C%E3%81%82%E3%82%8C%E3%81%B0%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E8%BF%94%E3%81%99%0A%20%20%20%20if%20mem%5Bi%5D%5Bc%5D%20%21%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%5Bc%5D%0A%20%20%20%20%23%20%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AF%E3%80%81%E5%85%A5%E3%82%8C%E3%81%AA%E3%81%84%E9%81%B8%E6%8A%9E%E3%81%97%E3%81%8B%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84%0A%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20return%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20%23%20%E5%93%81%E7%89%A9%20i%20%E3%82%92%E5%85%A5%E3%82%8C%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E5%85%A5%E3%82%8C%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AE%E6%9C%80%E5%A4%A7%E4%BE%A1%E5%80%A4%E3%82%92%E8%A8%88%E7%AE%97%E3%81%99%E3%82%8B%0A%20%20%20%20no%20%3D%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20i%20-%201%2C%20c%29%0A%20%20%20%20yes%20%3D%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20i%20-%201%2C%20c%20-%20wgt%5Bi%20-%201%5D%29%20%2B%20val%5Bi%20-%201%5D%0A%20%20%20%20%23%202%20%E3%81%A4%E3%81%AE%E6%A1%88%E3%81%AE%E3%81%86%E3%81%A1%E4%BE%A1%E5%80%A4%E3%81%8C%E5%A4%A7%E3%81%8D%E3%81%84%E6%96%B9%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%97%E3%81%A6%E8%BF%94%E3%81%99%0A%20%20%20%20mem%5Bi%5D%5Bc%5D%20%3D%20max%28no%2C%20yes%29%0A%20%20%20%20return%20mem%5Bi%5D%5Bc%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E3%83%A1%E3%83%A2%E5%8C%96%E6%8E%A2%E7%B4%A2%0A%20%20%20%20mem%20%3D%20%5B%5B-1%5D%20%2A%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20res%20%3D%20knapsack_dfs_mem%28wgt%2C%20val%2C%20mem%2C%20n%2C%20cap%29%0A%20%20%20%20print%28f%22%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%81%AA%E3%81%84%E6%9C%80%E5%A4%A7%E4%BE%A1%E5%80%A4%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=20&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20knapsack_dp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%EF%BC%9A%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281%2C%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%AA%E3%82%89%E5%93%81%E7%89%A9%20i%20%E3%81%AF%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20dp%5Bi%20-%201%5D%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%93%81%E7%89%A9%20i%20%E3%82%92%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E9%81%B8%E3%81%B6%E5%A0%B4%E5%90%88%E3%81%AE%E5%A4%A7%E3%81%8D%E3%81%84%E6%96%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20max%28dp%5Bi%20-%201%5D%5Bc%5D%2C%20dp%5Bi%20-%201%5D%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bcap%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20knapsack_dp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%81%AA%E3%81%84%E6%9C%80%E5%A4%A7%E4%BE%A1%E5%80%A4%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20knapsack_dp_comp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%220-1%20%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28cap%20%2B%201%29%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%80%86%E9%A0%86%E3%81%AB%E8%B5%B0%E6%9F%BB%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%28cap%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%AA%E3%82%89%E5%93%81%E7%89%A9%20i%20%E3%81%AF%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20dp%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%93%81%E7%89%A9%20i%20%E3%82%92%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E9%81%B8%E3%81%B6%E5%A0%B4%E5%90%88%E3%81%AE%E5%A4%A7%E3%81%8D%E3%81%84%E6%96%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20max%28dp%5Bc%5D%2C%20dp%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bcap%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20knapsack_dp_comp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%81%AA%E3%81%84%E6%9C%80%E5%A4%A7%E4%BE%A1%E5%80%A4%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_dynamic_programming/min_cost_climbing_stairs_dp.md b/ja/codes/pythontutor/chapter_dynamic_programming/min_cost_climbing_stairs_dp.md new file mode 100644 index 000000000..55cfb9315 --- /dev/null +++ b/ja/codes/pythontutor/chapter_dynamic_programming/min_cost_climbing_stairs_dp.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20min_cost_climbing_stairs_dp%28cost%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8E%E6%AE%B5%E7%99%BB%E3%82%8A%E3%81%AE%E6%9C%80%E5%B0%8F%E3%82%B3%E3%82%B9%E3%83%88%EF%BC%9A%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%20%3D%20len%28cost%29%20-%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20cost%5Bn%5D%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%AE%E8%A7%A3%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AB%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28n%20%2B%201%29%0A%20%20%20%20%23%20%E5%88%9D%E6%9C%9F%E7%8A%B6%E6%85%8B%EF%BC%9A%E6%9C%80%E5%B0%8F%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%AE%E8%A7%A3%E3%82%92%E3%81%82%E3%82%89%E3%81%8B%E3%81%98%E3%82%81%E8%A8%AD%E5%AE%9A%0A%20%20%20%20dp%5B1%5D%2C%20dp%5B2%5D%20%3D%20cost%5B1%5D%2C%20cost%5B2%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%B0%8F%E3%81%95%E3%81%84%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%8B%E3%82%89%E5%A4%A7%E3%81%8D%E3%81%84%E9%83%A8%E5%88%86%E5%95%8F%E9%A1%8C%E3%81%B8%E9%A0%86%E3%81%AB%E8%A7%A3%E3%81%8F%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%20%3D%20min%28dp%5Bi%20-%201%5D%2C%20dp%5Bi%20-%202%5D%29%20%2B%20cost%5Bi%5D%0A%20%20%20%20return%20dp%5Bn%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20cost%20%3D%20%5B0%2C%201%2C%2010%2C%201%2C%201%2C%201%2C%2010%2C%201%2C%201%2C%2010%2C%201%5D%0A%20%20%20%20print%28f%22%E5%85%A5%E5%8A%9B%E3%81%95%E3%82%8C%E3%81%9F%E9%9A%8E%E6%AE%B5%E3%82%B3%E3%82%B9%E3%83%88%E3%81%AE%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AF%20%7Bcost%7D%22%29%0A%0A%20%20%20%20res%20%3D%20min_cost_climbing_stairs_dp%28cost%29%0A%20%20%20%20print%28f%22%E9%9A%8E%E6%AE%B5%E3%82%92%E4%B8%8A%E3%82%8A%E5%88%87%E3%82%8B%E6%9C%80%E5%B0%8F%E3%82%B3%E3%82%B9%E3%83%88%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20min_cost_climbing_stairs_dp_comp%28cost%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E9%9A%8E%E6%AE%B5%E6%98%87%E3%82%8A%E3%81%AE%E6%9C%80%E5%B0%8F%E3%82%B3%E3%82%B9%E3%83%88%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%20%3D%20len%28cost%29%20-%201%0A%20%20%20%20if%20n%20%3D%3D%201%20or%20n%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20return%20cost%5Bn%5D%0A%20%20%20%20a%2C%20b%20%3D%20cost%5B1%5D%2C%20cost%5B2%5D%0A%20%20%20%20for%20i%20in%20range%283%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20a%2C%20b%20%3D%20b%2C%20min%28a%2C%20b%29%20%2B%20cost%5Bi%5D%0A%20%20%20%20return%20b%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20cost%20%3D%20%5B0%2C%201%2C%2010%2C%201%2C%201%2C%201%2C%2010%2C%201%2C%201%2C%2010%2C%201%5D%0A%20%20%20%20print%28f%22%E5%85%A5%E5%8A%9B%E3%81%95%E3%82%8C%E3%81%9F%E9%9A%8E%E6%AE%B5%E3%82%B3%E3%82%B9%E3%83%88%E3%81%AE%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AF%20%7Bcost%7D%22%29%0A%0A%20%20%20%20res%20%3D%20min_cost_climbing_stairs_dp_comp%28cost%29%0A%20%20%20%20print%28f%22%E9%9A%8E%E6%AE%B5%E3%82%92%E4%B8%8A%E3%82%8A%E5%88%87%E3%82%8B%E6%9C%80%E5%B0%8F%E3%82%B3%E3%82%B9%E3%83%88%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_dynamic_programming/min_path_sum.md b/ja/codes/pythontutor/chapter_dynamic_programming/min_path_sum.md new file mode 100644 index 000000000..70b8e681c --- /dev/null +++ b/ja/codes/pythontutor/chapter_dynamic_programming/min_path_sum.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dfs%28grid%3A%20list%5Blist%5Bint%5D%5D%2C%20i%3A%20int%2C%20j%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E5%92%8C%EF%BC%9A%E5%85%A8%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E5%B7%A6%E4%B8%8A%E3%81%AE%E3%82%BB%E3%83%AB%E3%81%AA%E3%82%89%E6%8E%A2%E7%B4%A2%E3%82%92%E7%B5%82%E4%BA%86%E3%81%99%E3%82%8B%0A%20%20%20%20if%20i%20%3D%3D%200%20and%20j%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E8%A1%8C%E3%81%BE%E3%81%9F%E3%81%AF%E5%88%97%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%8C%E7%AF%84%E5%9B%B2%E5%A4%96%E3%81%AA%E3%82%89%E3%80%81%E3%82%B3%E3%82%B9%E3%83%88%20%2B%E2%88%9E%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20inf%0A%20%20%20%20%23%20%E5%B7%A6%E4%B8%8A%E3%81%8B%E3%82%89%20%28i-1%2C%20j%29%20%E3%81%8A%E3%82%88%E3%81%B3%20%28i%2C%20j-1%29%20%E3%81%BE%E3%81%A7%E3%81%AE%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E3%82%B3%E3%82%B9%E3%83%88%E3%82%92%E8%A8%88%E7%AE%97%E3%81%99%E3%82%8B%0A%20%20%20%20up%20%3D%20min_path_sum_dfs%28grid%2C%20i%20-%201%2C%20j%29%0A%20%20%20%20left%20%3D%20min_path_sum_dfs%28grid%2C%20i%2C%20j%20-%201%29%0A%20%20%20%20%23%20%E5%B7%A6%E4%B8%8A%E9%9A%85%E3%81%8B%E3%82%89%20%28i%2C%20j%29%20%E3%81%BE%E3%81%A7%E3%81%AE%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E3%82%B3%E3%82%B9%E3%83%88%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20min%28left%2C%20up%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E5%85%A8%E6%8E%A2%E7%B4%A2%0A%20%20%20%20res%20%3D%20min_path_sum_dfs%28grid%2C%20n%20-%201%2C%20m%20-%201%29%0A%20%20%20%20print%28f%22%E5%B7%A6%E4%B8%8A%E3%81%8B%E3%82%89%E5%8F%B3%E4%B8%8B%E3%81%BE%E3%81%A7%E3%81%AE%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E5%92%8C%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dfs_mem%28%0A%20%20%20%20grid%3A%20list%5Blist%5Bint%5D%5D%2C%20mem%3A%20list%5Blist%5Bint%5D%5D%2C%20i%3A%20int%2C%20j%3A%20int%0A%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E5%92%8C%EF%BC%9A%E3%83%A1%E3%83%A2%E5%8C%96%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E5%B7%A6%E4%B8%8A%E3%81%AE%E3%82%BB%E3%83%AB%E3%81%AA%E3%82%89%E6%8E%A2%E7%B4%A2%E3%82%92%E7%B5%82%E4%BA%86%E3%81%99%E3%82%8B%0A%20%20%20%20if%20i%20%3D%3D%200%20and%20j%20%3D%3D%200%3A%0A%20%20%20%20%20%20%20%20return%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E8%A1%8C%E3%81%BE%E3%81%9F%E3%81%AF%E5%88%97%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%8C%E7%AF%84%E5%9B%B2%E5%A4%96%E3%81%AA%E3%82%89%E3%80%81%E3%82%B3%E3%82%B9%E3%83%88%20%2B%E2%88%9E%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%3A%0A%20%20%20%20%20%20%20%20return%20inf%0A%20%20%20%20%23%20%E6%97%A2%E3%81%AB%E8%A8%98%E9%8C%B2%E3%81%8C%E3%81%82%E3%82%8C%E3%81%B0%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E8%BF%94%E3%81%99%0A%20%20%20%20if%20mem%5Bi%5D%5Bj%5D%20%21%3D%20-1%3A%0A%20%20%20%20%20%20%20%20return%20mem%5Bi%5D%5Bj%5D%0A%20%20%20%20%23%20%E5%B7%A6%E3%81%A8%E4%B8%8A%E3%81%AE%E3%82%BB%E3%83%AB%E3%81%8B%E3%82%89%E3%81%AE%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E3%82%B3%E3%82%B9%E3%83%88%0A%20%20%20%20up%20%3D%20min_path_sum_dfs_mem%28grid%2C%20mem%2C%20i%20-%201%2C%20j%29%0A%20%20%20%20left%20%3D%20min_path_sum_dfs_mem%28grid%2C%20mem%2C%20i%2C%20j%20-%201%29%0A%20%20%20%20%23%20%E5%B7%A6%E4%B8%8A%E3%81%8B%E3%82%89%20%28i%2C%20j%29%20%E3%81%BE%E3%81%A7%E3%81%AE%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E3%82%B3%E3%82%B9%E3%83%88%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%97%E3%81%A6%E8%BF%94%E3%81%99%0A%20%20%20%20mem%5Bi%5D%5Bj%5D%20%3D%20min%28left%2C%20up%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20mem%5Bi%5D%5Bj%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%23%20%E3%83%A1%E3%83%A2%E5%8C%96%E6%8E%A2%E7%B4%A2%0A%20%20%20%20mem%20%3D%20%5B%5B-1%5D%20%2A%20m%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20res%20%3D%20min_path_sum_dfs_mem%28grid%2C%20mem%2C%20n%20-%201%2C%20m%20-%201%29%0A%20%20%20%20print%28f%22%E5%B7%A6%E4%B8%8A%E3%81%8B%E3%82%89%E5%8F%B3%E4%B8%8B%E3%81%BE%E3%81%A7%E3%81%AE%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E5%92%8C%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=16&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dp%28grid%3A%20list%5Blist%5Bint%5D%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E5%92%8C%EF%BC%9A%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%20%20%20%20%23%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20m%20for%20_%20in%20range%28n%29%5D%0A%20%20%20%20dp%5B0%5D%5B0%5D%20%3D%20grid%5B0%5D%5B0%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%85%88%E9%A0%AD%E8%A1%8C%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20dp%5B0%5D%5Bj%5D%20%3D%20dp%5B0%5D%5Bj%20-%201%5D%20%2B%20grid%5B0%5D%5Bj%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%85%88%E9%A0%AD%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bi%5D%5B0%5D%20%3D%20dp%5Bi%20-%201%5D%5B0%5D%20%2B%20grid%5Bi%5D%5B0%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%3A%20%E6%AE%8B%E3%82%8A%E3%81%AE%E8%A1%8C%E3%81%A8%E5%88%97%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bj%5D%20%3D%20min%28dp%5Bi%5D%5Bj%20-%201%5D%2C%20dp%5Bi%20-%201%5D%5Bj%5D%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20dp%5Bn%20-%201%5D%5Bm%20-%201%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20min_path_sum_dp%28grid%29%0A%20%20%20%20print%28f%22%E5%B7%A6%E4%B8%8A%E3%81%8B%E3%82%89%E5%8F%B3%E4%B8%8B%E3%81%BE%E3%81%A7%E3%81%AE%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E5%92%8C%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=from%20math%20import%20inf%0A%0Adef%20min_path_sum_dp_comp%28grid%3A%20list%5Blist%5Bint%5D%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E5%92%8C%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%20%20%20%20%23%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20m%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%85%88%E9%A0%AD%E8%A1%8C%0A%20%20%20%20dp%5B0%5D%20%3D%20grid%5B0%5D%5B0%5D%0A%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20dp%5Bj%20-%201%5D%20%2B%20grid%5B0%5D%5Bj%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E6%AE%8B%E3%82%8A%E3%81%AE%E8%A1%8C%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E5%85%88%E9%A0%AD%E5%88%97%0A%20%20%20%20%20%20%20%20dp%5B0%5D%20%3D%20dp%5B0%5D%20%2B%20grid%5Bi%5D%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%EF%BC%9A%E6%AE%8B%E3%82%8A%E3%81%AE%E5%88%97%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%281%2C%20m%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bj%5D%20%3D%20min%28dp%5Bj%20-%201%5D%2C%20dp%5Bj%5D%29%20%2B%20grid%5Bi%5D%5Bj%5D%0A%20%20%20%20return%20dp%5Bm%20-%201%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20grid%20%3D%20%5B%5B1%2C%203%2C%201%2C%205%5D%2C%20%5B2%2C%202%2C%204%2C%202%5D%2C%20%5B5%2C%203%2C%202%2C%201%5D%2C%20%5B4%2C%203%2C%205%2C%202%5D%5D%0A%20%20%20%20n%2C%20m%20%3D%20len%28grid%29%2C%20len%28grid%5B0%5D%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20min_path_sum_dp_comp%28grid%29%0A%20%20%20%20print%28f%22%E5%B7%A6%E4%B8%8A%E3%81%8B%E3%82%89%E5%8F%B3%E4%B8%8B%E3%81%BE%E3%81%A7%E3%81%AE%E6%9C%80%E5%B0%8F%E7%B5%8C%E8%B7%AF%E5%92%8C%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_dynamic_programming/unbounded_knapsack.md b/ja/codes/pythontutor/chapter_dynamic_programming/unbounded_knapsack.md new file mode 100644 index 000000000..637f60c00 --- /dev/null +++ b/ja/codes/pythontutor/chapter_dynamic_programming/unbounded_knapsack.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20unbounded_knapsack_dp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AE%8C%E5%85%A8%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%95%8F%E9%A1%8C%EF%BC%9A%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B%5B0%5D%20%2A%20%28cap%20%2B%201%29%20for%20_%20in%20range%28n%20%2B%201%29%5D%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281%2C%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%AA%E3%82%89%E5%93%81%E7%89%A9%20i%20%E3%81%AF%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20dp%5Bi%20-%201%5D%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%93%81%E7%89%A9%20i%20%E3%82%92%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E9%81%B8%E3%81%B6%E5%A0%B4%E5%90%88%E3%81%AE%E5%A4%A7%E3%81%8D%E3%81%84%E6%96%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bi%5D%5Bc%5D%20%3D%20max%28dp%5Bi%20-%201%5D%5Bc%5D%2C%20dp%5Bi%5D%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bn%5D%5Bcap%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B1%2C%202%2C%203%5D%0A%20%20%20%20val%20%3D%20%5B5%2C%2011%2C%2015%5D%0A%20%20%20%20cap%20%3D%204%0A%0A%20%20%20%20%23%20%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20unbounded_knapsack_dp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%81%AA%E3%81%84%E6%9C%80%E5%A4%A7%E4%BE%A1%E5%80%A4%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20unbounded_knapsack_dp_comp%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%AE%8C%E5%85%A8%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%95%8F%E9%A1%8C%EF%BC%9A%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%22%22%22%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%20%20%20%20%23%20dp%20%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20dp%20%3D%20%5B0%5D%20%2A%20%28cap%20%2B%201%29%0A%20%20%20%20%23%20%E7%8A%B6%E6%85%8B%E9%81%B7%E7%A7%BB%0A%20%20%20%20for%20i%20in%20range%281%2C%20n%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%A0%86%E6%96%B9%E5%90%91%E3%81%AB%E8%B5%B0%E6%9F%BB%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20for%20c%20in%20range%281%2C%20cap%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20wgt%5Bi%20-%201%5D%20%3E%20c%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%AA%E3%82%89%E5%93%81%E7%89%A9%20i%20%E3%81%AF%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20dp%5Bc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%93%81%E7%89%A9%20i%20%E3%82%92%E9%81%B8%E3%81%B0%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%A8%E9%81%B8%E3%81%B6%E5%A0%B4%E5%90%88%E3%81%AE%E5%A4%A7%E3%81%8D%E3%81%84%E6%96%B9%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dp%5Bc%5D%20%3D%20max%28dp%5Bc%5D%2C%20dp%5Bc%20-%20wgt%5Bi%20-%201%5D%5D%20%2B%20val%5Bi%20-%201%5D%29%0A%20%20%20%20return%20dp%5Bcap%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B1%2C%202%2C%203%5D%0A%20%20%20%20val%20%3D%20%5B5%2C%2011%2C%2015%5D%0A%20%20%20%20cap%20%3D%204%0A%0A%20%20%20%20%23%20%E7%A9%BA%E9%96%93%E6%9C%80%E9%81%A9%E5%8C%96%E5%BE%8C%E3%81%AE%E5%8B%95%E7%9A%84%E8%A8%88%E7%94%BB%E6%B3%95%0A%20%20%20%20res%20%3D%20unbounded_knapsack_dp_comp%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%81%AA%E3%81%84%E6%9C%80%E5%A4%A7%E4%BE%A1%E5%80%A4%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_graph/graph_adjacency_list.md b/ja/codes/pythontutor/chapter_graph/graph_adjacency_list.md new file mode 100644 index 000000000..796adaa24 --- /dev/null +++ b/ja/codes/pythontutor/chapter_graph/graph_adjacency_list.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Vertex%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%27Vertex%27%5D%3A%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex%2C%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D%2C%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20len%28self.adj_list%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20remove_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.remove%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.remove%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20def%20remove_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20if%20vet%20not%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list.pop%28vet%29%0A%20%20%20%20%20%20%20%20for%20vertex%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%5Bvertex%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.adj_list%5Bvertex%5D.remove%28vet%29%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B1%2C%203%2C%202%2C%205%2C%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%5Bv%5B0%5D%2C%20v%5B1%5D%5D%2C%20%5Bv%5B0%5D%2C%20v%5B3%5D%5D%2C%20%5Bv%5B1%5D%2C%20v%5B2%5D%5D%2C%20%5Bv%5B2%5D%2C%20v%5B3%5D%5D%2C%20%5Bv%5B2%5D%2C%20v%5B4%5D%5D%2C%20%5Bv%5B3%5D%2C%20v%5B4%5D%5D%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%20%20%20%20del%20edges%0A%20%20%20%20graph.add_edge%28v%5B0%5D%2C%20v%5B2%5D%29%0A%20%20%20%20graph.remove_edge%28v%5B0%5D%2C%20v%5B1%5D%29%0A%20%20%20%20v5%20%3D%20Vertex%286%29%0A%20%20%20%20graph.add_vertex%28v5%29%0A%20%20%20%20graph.remove_vertex%28v%5B1%5D%29&cumulative=false&curInstr=39&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_graph/graph_adjacency_matrix.md b/ja/codes/pythontutor/chapter_graph/graph_adjacency_matrix.md new file mode 100644 index 000000000..17fbdb022 --- /dev/null +++ b/ja/codes/pythontutor/chapter_graph/graph_adjacency_matrix.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20GraphAdjMat%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20vertices%3A%20list%5Bint%5D%2C%20edges%3A%20list%5Blist%5Bint%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20self.vertices%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.adj_mat%3A%20list%5Blist%5Bint%5D%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20val%20in%20vertices%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28val%29%0A%20%20%20%20%20%20%20%20for%20e%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28e%5B0%5D%2C%20e%5B1%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20len%28self.vertices%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20n%20%3D%20self.size%28%29%0A%20%20%20%20%20%20%20%20self.vertices.append%28val%29%0A%20%20%20%20%20%20%20%20new_row%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20%20%20%20%20self.adj_mat.append%28new_row%29%0A%20%20%20%20%20%20%20%20for%20row%20in%20self.adj_mat%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row.append%280%29%0A%0A%20%20%20%20def%20remove_vertex%28self%2C%20index%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20index%20%3E%3D%20self.size%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.vertices.pop%28index%29%0A%20%20%20%20%20%20%20%20self.adj_mat.pop%28index%29%0A%20%20%20%20%20%20%20%20for%20row%20in%20self.adj_mat%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20row.pop%28index%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%20or%20%28j%20%3E%3D%20self.size%28%29%29%20or%20%28i%20%3D%3D%20j%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bi%5D%5Bj%5D%20%3D%201%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bj%5D%5Bi%5D%20%3D%201%0A%0A%20%20%20%20def%20remove_edge%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20j%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%20or%20%28j%20%3E%3D%20self.size%28%29%29%20or%20%28i%20%3D%3D%20j%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%29%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bi%5D%5Bj%5D%20%3D%200%0A%20%20%20%20%20%20%20%20self.adj_mat%5Bj%5D%5Bi%5D%20%3D%200%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20vertices%20%3D%20%5B1%2C%203%2C%202%2C%205%2C%204%5D%0A%20%20%20%20edges%20%3D%20%5B%5B0%2C%201%5D%2C%20%5B0%2C%203%5D%2C%20%5B1%2C%202%5D%2C%20%5B2%2C%203%5D%2C%20%5B2%2C%204%5D%2C%20%5B3%2C%204%5D%5D%0A%20%20%20%20graph%20%3D%20GraphAdjMat%28vertices%2C%20edges%29%0A%20%20%20%20graph.add_edge%280%2C%202%29%0A%20%20%20%20graph.remove_edge%280%2C%201%29%0A%20%20%20%20graph.add_vertex%286%29%0A%20%20%20%20graph.remove_vertex%281%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_graph/graph_bfs.md b/ja/codes/pythontutor/chapter_graph/graph_bfs.md new file mode 100644 index 000000000..35c8851c8 --- /dev/null +++ b/ja/codes/pythontutor/chapter_graph/graph_bfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0Aclass%20Vertex%3A%0A%20%20%20%20%22%22%22%E9%A0%82%E7%82%B9%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%22Vertex%22%5D%3A%0A%20%20%20%20%22%22%22%E5%80%A4%E3%83%AA%E3%82%B9%E3%83%88%20vals%20%E3%82%92%E5%85%A5%E5%8A%9B%E3%81%97%E3%80%81%E9%A0%82%E7%82%B9%E3%83%AA%E3%82%B9%E3%83%88%20vets%20%E3%82%92%E8%BF%94%E3%81%99%22%22%22%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%20%20%20%20%22%22%22%E9%9A%A3%E6%8E%A5%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AB%E5%9F%BA%E3%81%A5%E3%81%8F%E7%84%A1%E5%90%91%E3%82%B0%E3%83%A9%E3%83%95%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%22%22%22%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex%2C%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D%2C%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BE%BA%E3%82%92%E8%BF%BD%E5%8A%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E9%A0%82%E7%82%B9%E3%82%92%E8%BF%BD%E5%8A%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%0Adef%20graph_bfs%28graph%3A%20GraphAdjList%2C%20start_vet%3A%20Vertex%29%20-%3E%20list%5BVertex%5D%3A%0A%20%20%20%20%22%22%22%E5%B9%85%E5%84%AA%E5%85%88%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E9%A0%82%E7%82%B9%E3%81%AE%E8%B5%B0%E6%9F%BB%E9%A0%86%E5%BA%8F%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E6%B8%88%E3%81%BF%E3%81%AE%E9%A0%82%E7%82%B9%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%0A%20%20%20%20visited%20%3D%20set%5BVertex%5D%28%5Bstart_vet%5D%29%0A%20%20%20%20%23%20BFS%20%E3%81%AE%E5%AE%9F%E8%A3%85%E3%81%AB%E3%82%AD%E3%83%A5%E3%83%BC%E3%82%92%E7%94%A8%E3%81%84%E3%82%8B%0A%20%20%20%20que%20%3D%20deque%5BVertex%5D%28%5Bstart_vet%5D%29%0A%20%20%20%20%23%20%E9%A0%82%E7%82%B9%20vet%20%E3%82%92%E8%B5%B7%E7%82%B9%E3%81%AB%E3%80%81%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%A0%82%E7%82%B9%E3%82%92%E8%A8%AA%E5%95%8F%E3%81%97%E7%B5%82%E3%81%88%E3%82%8B%E3%81%BE%E3%81%A7%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%99%0A%20%20%20%20while%20len%28que%29%20%3E%200%3A%0A%20%20%20%20%20%20%20%20vet%20%3D%20que.popleft%28%29%20%20%23%20%E5%85%88%E9%A0%AD%E3%81%AE%E9%A0%82%E7%82%B9%E3%82%92%E3%83%87%E3%82%AD%E3%83%A5%E3%83%BC%0A%20%20%20%20%20%20%20%20res.append%28vet%29%20%20%23%20%E8%A8%AA%E5%95%8F%E3%81%97%E3%81%9F%E9%A0%82%E7%82%B9%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20%20%20%20%20%23%20%E3%81%93%E3%81%AE%E9%A0%82%E7%82%B9%E3%81%AE%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%9A%A3%E6%8E%A5%E9%A0%82%E7%82%B9%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20%20%20%20%20for%20adj_vet%20in%20graph.adj_list%5Bvet%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20adj_vet%20in%20visited%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%20%20%23%20%E8%A8%AA%E5%95%8F%E6%B8%88%E3%81%BF%E3%81%AE%E9%A0%82%E7%82%B9%E3%82%92%E3%82%B9%E3%82%AD%E3%83%83%E3%83%97%0A%20%20%20%20%20%20%20%20%20%20%20%20que.append%28adj_vet%29%20%20%23%20%E6%9C%AA%E8%A8%AA%E5%95%8F%E3%81%AE%E9%A0%82%E7%82%B9%E3%81%AE%E3%81%BF%E3%82%92%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%AB%E8%BF%BD%E5%8A%A0%0A%20%20%20%20%20%20%20%20%20%20%20%20visited.add%28adj_vet%29%20%20%23%20%E3%81%93%E3%81%AE%E9%A0%82%E7%82%B9%E3%82%92%E8%A8%AA%E5%95%8F%E6%B8%88%E3%81%BF%E3%81%AB%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E9%A0%82%E7%82%B9%E3%81%AE%E8%B5%B0%E6%9F%BB%E9%A0%86%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E7%84%A1%E5%90%91%E3%82%B0%E3%83%A9%E3%83%95%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B0%2C%201%2C%202%2C%203%2C%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D%2C%20v%5B1%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D%2C%20v%5B3%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D%2C%20v%5B2%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D%2C%20v%5B4%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B3%5D%2C%20v%5B4%5D%5D%2C%0A%20%20%20%20%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%20%20%20%20del%20edges%0A%0A%20%20%20%20%23%20%E5%B9%85%E5%84%AA%E5%85%88%E6%8E%A2%E7%B4%A2%0A%20%20%20%20res%20%3D%20graph_bfs%28graph%2C%20v%5B0%5D%29&cumulative=false&curInstr=131&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_graph/graph_dfs.md b/ja/codes/pythontutor/chapter_graph/graph_dfs.md new file mode 100644 index 000000000..e028ff407 --- /dev/null +++ b/ja/codes/pythontutor/chapter_graph/graph_dfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Vertex%3A%0A%20%20%20%20%22%22%22%E9%A0%82%E7%82%B9%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Adef%20vals_to_vets%28vals%3A%20list%5Bint%5D%29%20-%3E%20list%5B%22Vertex%22%5D%3A%0A%20%20%20%20%22%22%22%E5%80%A4%E3%83%AA%E3%82%B9%E3%83%88%20vals%20%E3%82%92%E5%85%A5%E5%8A%9B%E3%81%97%E3%80%81%E9%A0%82%E7%82%B9%E3%83%AA%E3%82%B9%E3%83%88%20vets%20%E3%82%92%E8%BF%94%E3%81%99%22%22%22%0A%20%20%20%20return%20%5BVertex%28val%29%20for%20val%20in%20vals%5D%0A%0Aclass%20GraphAdjList%3A%0A%20%20%20%20%22%22%22%E9%9A%A3%E6%8E%A5%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AB%E5%9F%BA%E3%81%A5%E3%81%8F%E7%84%A1%E5%90%91%E3%82%B0%E3%83%A9%E3%83%95%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20edges%3A%20list%5Blist%5BVertex%5D%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%22%22%22%0A%20%20%20%20%20%20%20%20self.adj_list%20%3D%20dict%5BVertex%2C%20list%5BVertex%5D%5D%28%29%0A%20%20%20%20%20%20%20%20for%20edge%20in%20edges%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B0%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_vertex%28edge%5B1%5D%29%0A%20%20%20%20%20%20%20%20%20%20%20%20self.add_edge%28edge%5B0%5D%2C%20edge%5B1%5D%29%0A%0A%20%20%20%20def%20add_edge%28self%2C%20vet1%3A%20Vertex%2C%20vet2%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BE%BA%E3%82%92%E8%BF%BD%E5%8A%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet1%20not%20in%20self.adj_list%20or%20vet2%20not%20in%20self.adj_list%20or%20vet1%20%3D%3D%20vet2%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet1%5D.append%28vet2%29%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet2%5D.append%28vet1%29%0A%0A%20%20%20%20def%20add_vertex%28self%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E9%A0%82%E7%82%B9%E3%82%92%E8%BF%BD%E5%8A%A0%22%22%22%0A%20%20%20%20%20%20%20%20if%20vet%20in%20self.adj_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20self.adj_list%5Bvet%5D%20%3D%20%5B%5D%0A%0A%0Adef%20dfs%28graph%3A%20GraphAdjList%2C%20visited%3A%20set%5BVertex%5D%2C%20res%3A%20list%5BVertex%5D%2C%20vet%3A%20Vertex%29%3A%0A%20%20%20%20%22%22%22%E6%B7%B1%E3%81%95%E5%84%AA%E5%85%88%E8%B5%B0%E6%9F%BB%E3%81%AE%E8%A3%9C%E5%8A%A9%E9%96%A2%E6%95%B0%22%22%22%0A%20%20%20%20res.append%28vet%29%20%20%23%20%E8%A8%AA%E5%95%8F%E3%81%97%E3%81%9F%E9%A0%82%E7%82%B9%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20visited.add%28vet%29%20%20%23%20%E3%81%93%E3%81%AE%E9%A0%82%E7%82%B9%E3%82%92%E8%A8%AA%E5%95%8F%E6%B8%88%E3%81%BF%E3%81%AB%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E3%81%93%E3%81%AE%E9%A0%82%E7%82%B9%E3%81%AE%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E9%9A%A3%E6%8E%A5%E9%A0%82%E7%82%B9%E3%82%92%E8%B5%B0%E6%9F%BB%0A%20%20%20%20for%20adjVet%20in%20graph.adj_list%5Bvet%5D%3A%0A%20%20%20%20%20%20%20%20if%20adjVet%20in%20visited%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%20%20%23%20%E8%A8%AA%E5%95%8F%E6%B8%88%E3%81%BF%E3%81%AE%E9%A0%82%E7%82%B9%E3%82%92%E3%82%B9%E3%82%AD%E3%83%83%E3%83%97%0A%20%20%20%20%20%20%20%20%23%20%E9%9A%A3%E6%8E%A5%E9%A0%82%E7%82%B9%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E8%A8%AA%E5%95%8F%0A%20%20%20%20%20%20%20%20dfs%28graph%2C%20visited%2C%20res%2C%20adjVet%29%0A%0A%0Adef%20graph_dfs%28graph%3A%20GraphAdjList%2C%20start_vet%3A%20Vertex%29%20-%3E%20list%5BVertex%5D%3A%0A%20%20%20%20%22%22%22%E6%B7%B1%E3%81%95%E5%84%AA%E5%85%88%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E6%8C%87%E5%AE%9A%E3%81%97%E3%81%9F%E9%A0%82%E7%82%B9%E3%81%AE%E9%9A%A3%E6%8E%A5%E9%A0%82%E7%82%B9%E3%82%92%E3%81%99%E3%81%B9%E3%81%A6%E5%8F%96%E5%BE%97%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%80%81%E9%9A%A3%E6%8E%A5%E3%83%AA%E3%82%B9%E3%83%88%E3%81%A7%E3%82%B0%E3%83%A9%E3%83%95%E3%82%92%E8%A1%A8%E7%8F%BE%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E9%A0%82%E7%82%B9%E3%81%AE%E8%B5%B0%E6%9F%BB%E9%A0%86%E5%BA%8F%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E6%B8%88%E3%81%BF%E3%81%AE%E9%A0%82%E7%82%B9%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%0A%20%20%20%20visited%20%3D%20set%5BVertex%5D%28%29%0A%20%20%20%20dfs%28graph%2C%20visited%2C%20res%2C%20start_vet%29%0A%20%20%20%20return%20res%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E7%84%A1%E5%90%91%E3%82%B0%E3%83%A9%E3%83%95%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20v%20%3D%20vals_to_vets%28%5B0%2C%201%2C%202%2C%203%2C%204%5D%29%0A%20%20%20%20edges%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D%2C%20v%5B1%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B0%5D%2C%20v%5B3%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D%2C%20v%5B2%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B1%5D%2C%20v%5B4%5D%5D%2C%0A%20%20%20%20%20%20%20%20%5Bv%5B3%5D%2C%20v%5B4%5D%5D%2C%0A%20%20%20%20%5D%0A%20%20%20%20graph%20%3D%20GraphAdjList%28edges%29%0A%0A%20%20%20%20%23%20%E6%B7%B1%E3%81%95%E5%84%AA%E5%85%88%E6%8E%A2%E7%B4%A2%0A%20%20%20%20res%20%3D%20graph_dfs%28graph%2C%20v%5B0%5D%29&cumulative=false&curInstr=130&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_greedy/coin_change_greedy.md b/ja/codes/pythontutor/chapter_greedy/coin_change_greedy.md new file mode 100644 index 000000000..e529b4b80 --- /dev/null +++ b/ja/codes/pythontutor/chapter_greedy/coin_change_greedy.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20coin_change_greedy%28coins%3A%20list%5Bint%5D%2C%20amt%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E3%82%B3%E3%82%A4%E3%83%B3%E4%BA%A4%E6%8F%9B%EF%BC%9A%E8%B2%AA%E6%AC%B2%E6%B3%95%22%22%22%0A%20%20%20%20%23%20coins%20%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AF%E3%82%BD%E3%83%BC%E3%83%88%E6%B8%88%E3%81%BF%E3%81%A8%E4%BB%AE%E5%AE%9A%E3%81%99%E3%82%8B%0A%20%20%20%20i%20%3D%20len%28coins%29%20-%201%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20%23%20%E6%AE%8B%E9%A1%8D%E3%81%8C%E3%81%AA%E3%81%8F%E3%81%AA%E3%82%8B%E3%81%BE%E3%81%A7%E8%B2%AA%E6%AC%B2%E9%81%B8%E6%8A%9E%E3%82%92%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%99%0A%20%20%20%20while%20amt%20%3E%200%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%AE%8B%E9%A1%8D%E4%BB%A5%E4%B8%8B%E3%81%A7%E6%9C%80%E3%82%82%E8%BF%91%E3%81%84%E7%A1%AC%E8%B2%A8%E3%82%92%E8%A6%8B%E3%81%A4%E3%81%91%E3%82%8B%0A%20%20%20%20%20%20%20%20while%20i%20%3E%200%20and%20coins%5Bi%5D%20%3E%20amt%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20-%3D%201%0A%20%20%20%20%20%20%20%20%23%20coins%5Bi%5D%20%E3%82%92%E9%81%B8%E6%8A%9E%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20amt%20-%3D%20coins%5Bi%5D%0A%20%20%20%20%20%20%20%20count%20%2B%3D%201%0A%20%20%20%20%23%20%E5%AE%9F%E8%A1%8C%E5%8F%AF%E8%83%BD%E3%81%AA%E8%A7%A3%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%82%89%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%20-1%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20count%20if%20amt%20%3D%3D%200%20else%20-1%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E8%B2%AA%E6%AC%B2%E6%B3%95%EF%BC%9A%E5%A4%A7%E5%9F%9F%E6%9C%80%E9%81%A9%E8%A7%A3%E3%82%92%E4%BF%9D%E8%A8%BC%E3%81%A7%E3%81%8D%E3%82%8B%0A%20%20%20%20coins%20%3D%20%5B1%2C%205%2C%2010%2C%2020%2C%2050%2C%20100%5D%0A%20%20%20%20amt%20%3D%20186%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D%2C%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%7Bamt%7D%20%E3%82%92%E4%BD%9C%E3%82%8B%E3%81%AE%E3%81%AB%E5%BF%85%E8%A6%81%E3%81%AA%E6%9C%80%E5%B0%8F%E3%81%AE%E7%A1%AC%E8%B2%A8%E6%9E%9A%E6%95%B0%E3%81%AF%20%7Bres%7D%22%29%0A%0A%20%20%20%20%23%20%E8%B2%AA%E6%AC%B2%E6%B3%95%EF%BC%9A%E5%A4%A7%E5%9F%9F%E6%9C%80%E9%81%A9%E8%A7%A3%E3%82%92%E4%BF%9D%E8%A8%BC%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84%0A%20%20%20%20coins%20%3D%20%5B1%2C%2020%2C%2050%5D%0A%20%20%20%20amt%20%3D%2060%0A%20%20%20%20res%20%3D%20coin_change_greedy%28coins%2C%20amt%29%0A%20%20%20%20print%28f%22%5Cncoins%20%3D%20%7Bcoins%7D%2C%20amt%20%3D%20%7Bamt%7D%22%29%0A%20%20%20%20print%28f%22%7Bamt%7D%20%E3%82%92%E4%BD%9C%E3%82%8B%E3%81%AE%E3%81%AB%E5%BF%85%E8%A6%81%E3%81%AA%E6%9C%80%E5%B0%8F%E3%81%AE%E7%A1%AC%E8%B2%A8%E6%9E%9A%E6%95%B0%E3%81%AF%20%7Bres%7D%22%29%0A%20%20%20%20print%28f%22%E5%AE%9F%E9%9A%9B%E3%81%AB%E5%BF%85%E8%A6%81%E3%81%AA%E6%9C%80%E5%B0%8F%E6%9E%9A%E6%95%B0%E3%81%AF%203%20%EF%BC%8C%E3%81%A4%E3%81%BE%E3%82%8A%2020%20%2B%2020%20%2B%2020%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_greedy/fractional_knapsack.md b/ja/codes/pythontutor/chapter_greedy/fractional_knapsack.md new file mode 100644 index 000000000..1f5d2d923 --- /dev/null +++ b/ja/codes/pythontutor/chapter_greedy/fractional_knapsack.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Item%3A%0A%20%20%20%20%22%22%22%E5%93%81%E7%89%A9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20w%3A%20int%2C%20v%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.w%20%3D%20w%20%20%23%20%E5%93%81%E7%89%A9%E3%81%AE%E9%87%8D%E3%81%95%0A%20%20%20%20%20%20%20%20self.v%20%3D%20v%20%20%23%20%E5%93%81%E7%89%A9%E3%81%AE%E4%BE%A1%E5%80%A4%0A%0Adef%20fractional_knapsack%28wgt%3A%20list%5Bint%5D%2C%20val%3A%20list%5Bint%5D%2C%20cap%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%88%86%E6%95%B0%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%EF%BC%9A%E8%B2%AA%E6%AC%B2%E6%B3%95%22%22%22%0A%20%20%20%20%23%20%E9%87%8D%E3%81%95%E3%81%A8%E4%BE%A1%E5%80%A4%E3%81%AE%202%20%E5%B1%9E%E6%80%A7%E3%82%92%E6%8C%81%E3%81%A4%E5%93%81%E7%89%A9%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BD%9C%E6%88%90%0A%20%20%20%20items%20%3D%20%5BItem%28w%2C%20v%29%20for%20w%2C%20v%20in%20zip%28wgt%2C%20val%29%5D%0A%20%20%20%20%23%20%E5%8D%98%E4%BD%8D%E4%BE%A1%E5%80%A4%20item.v%20%2F%20item.w%20%E3%81%AE%E9%AB%98%E3%81%84%E9%A0%86%E3%81%AB%E3%82%BD%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B%0A%20%20%20%20items.sort%28key%3Dlambda%20item%3A%20item.v%20%2F%20item.w%2C%20reverse%3DTrue%29%0A%20%20%20%20%23%20%E8%B2%AA%E6%AC%B2%E9%81%B8%E6%8A%9E%E3%82%92%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%99%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20for%20item%20in%20items%3A%0A%20%20%20%20%20%20%20%20if%20item.w%20%3C%3D%20cap%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%AE%8B%E3%82%8A%E5%AE%B9%E9%87%8F%E3%81%8C%E5%8D%81%E5%88%86%E3%81%AA%E3%82%89%E3%80%81%E7%8F%BE%E5%9C%A8%E3%81%AE%E5%93%81%E7%89%A9%E3%82%92%E4%B8%B8%E3%81%94%E3%81%A8%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E3%81%AB%E5%85%A5%E3%82%8C%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20item.v%0A%20%20%20%20%20%20%20%20%20%20%20%20cap%20-%3D%20item.w%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%AE%8B%E3%82%8A%E5%AE%B9%E9%87%8F%E3%81%8C%E8%B6%B3%E3%82%8A%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%AF%E3%80%81%E7%8F%BE%E5%9C%A8%E3%81%AE%E5%93%81%E7%89%A9%E3%81%AE%E4%B8%80%E9%83%A8%E3%81%A0%E3%81%91%E3%82%92%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E3%81%AB%E5%85%A5%E3%82%8C%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%2B%3D%20%28item.v%20%2F%20item.w%29%20%2A%20cap%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%AE%8B%E3%82%8A%E5%AE%B9%E9%87%8F%E3%81%8C%E3%81%AA%E3%81%84%E3%81%9F%E3%82%81%E3%80%81%E3%83%AB%E3%83%BC%E3%83%97%E3%82%92%E6%8A%9C%E3%81%91%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20wgt%20%3D%20%5B10%2C%2020%2C%2030%2C%2040%2C%2050%5D%0A%20%20%20%20val%20%3D%20%5B50%2C%20120%2C%20150%2C%20210%2C%20240%5D%0A%20%20%20%20cap%20%3D%2050%0A%20%20%20%20n%20%3D%20len%28wgt%29%0A%0A%20%20%20%20%23%20%E8%B2%AA%E6%AC%B2%E6%B3%95%0A%20%20%20%20res%20%3D%20fractional_knapsack%28wgt%2C%20val%2C%20cap%29%0A%20%20%20%20print%28f%22%E3%83%8A%E3%83%83%E3%83%97%E3%82%B5%E3%83%83%E3%82%AF%E5%AE%B9%E9%87%8F%E3%82%92%E8%B6%85%E3%81%88%E3%81%AA%E3%81%84%E6%9C%80%E5%A4%A7%E4%BE%A1%E5%80%A4%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_greedy/max_capacity.md b/ja/codes/pythontutor/chapter_greedy/max_capacity.md new file mode 100644 index 000000000..b44a5e8a1 --- /dev/null +++ b/ja/codes/pythontutor/chapter_greedy/max_capacity.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20max_capacity%28ht%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%EF%BC%9A%E8%B2%AA%E6%AC%B2%E6%B3%95%22%22%22%0A%20%20%20%20%23%20i%2C%20j%20%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%97%E3%80%81%E3%81%9D%E3%82%8C%E3%81%9E%E3%82%8C%E9%85%8D%E5%88%97%E3%81%AE%E4%B8%A1%E7%AB%AF%E3%81%AB%E7%BD%AE%E3%81%8F%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28ht%29%20-%201%0A%20%20%20%20%23%20%E5%88%9D%E6%9C%9F%E3%81%AE%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E3%81%AF%200%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%202%20%E6%9E%9A%E3%81%AE%E6%9D%BF%E3%81%8C%E5%87%BA%E4%BC%9A%E3%81%86%E3%81%BE%E3%81%A7%E8%B2%AA%E6%AC%B2%E9%81%B8%E6%8A%9E%E3%82%92%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%99%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E3%82%92%E6%9B%B4%E6%96%B0%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20cap%20%3D%20min%28ht%5Bi%5D%2C%20ht%5Bj%5D%29%20%2A%20%28j%20-%20i%29%0A%20%20%20%20%20%20%20%20res%20%3D%20max%28res%2C%20cap%29%0A%20%20%20%20%20%20%20%20%23%20%E7%9F%AD%E3%81%84%E6%96%B9%E3%82%92%E5%86%85%E5%81%B4%E3%81%B8%E5%8B%95%E3%81%8B%E3%81%99%0A%20%20%20%20%20%20%20%20if%20ht%5Bi%5D%20%3C%20ht%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20return%20res%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20ht%20%3D%20%5B3%2C%208%2C%205%2C%202%2C%207%2C%207%2C%203%2C%204%5D%0A%0A%20%20%20%20%23%20%E8%B2%AA%E6%AC%B2%E6%B3%95%0A%20%20%20%20res%20%3D%20max_capacity%28ht%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_greedy/max_product_cutting.md b/ja/codes/pythontutor/chapter_greedy/max_product_cutting.md new file mode 100644 index 000000000..dac718c3a --- /dev/null +++ b/ja/codes/pythontutor/chapter_greedy/max_product_cutting.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=import%20math%0A%0Adef%20max_product_cutting%28n%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E5%88%87%E6%96%AD%E7%A9%8D%EF%BC%9A%E8%B2%AA%E6%AC%B2%E6%B3%95%22%22%22%0A%20%20%20%20%23%20n%20%3C%3D%203%20%E3%81%AE%E3%81%A8%E3%81%8D%E3%81%AF%E3%80%81%E5%BF%85%E3%81%9A%201%20%E3%82%92%E5%88%87%E3%82%8A%E5%87%BA%E3%81%99%0A%20%20%20%20if%20n%20%3C%3D%203%3A%0A%20%20%20%20%20%20%20%20return%201%20%2A%20%28n%20-%201%29%0A%20%20%20%20%23%20%E8%B2%AA%E6%AC%B2%E3%81%AB%203%20%E3%82%92%E5%88%87%E3%82%8A%E5%87%BA%E3%81%97%E3%80%81a%20%E3%82%92%203%20%E3%81%AE%E5%80%8B%E6%95%B0%E3%80%81b%20%E3%82%92%E4%BD%99%E3%82%8A%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20a%2C%20b%20%3D%20n%20%2F%2F%203%2C%20n%20%25%203%0A%20%20%20%20if%20b%20%3D%3D%201%3A%0A%20%20%20%20%20%20%20%20%23%20%E4%BD%99%E3%82%8A%E3%81%8C%201%20%E3%81%AE%E3%81%A8%E3%81%8D%E3%81%AF%E3%80%811%20%2A%203%20%E3%82%92%202%20%2A%202%20%E3%81%AB%E5%A4%89%E3%81%88%E3%82%8B%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283%2C%20a%20-%201%29%29%20%2A%202%20%2A%202%0A%20%20%20%20if%20b%20%3D%3D%202%3A%0A%20%20%20%20%20%20%20%20%23%20%E4%BD%99%E3%82%8A%E3%81%8C%202%20%E3%81%AE%E3%81%A8%E3%81%8D%E3%81%AF%E3%80%81%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E3%81%AB%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20return%20int%28math.pow%283%2C%20a%29%29%20%2A%202%0A%20%20%20%20%23%20%E4%BD%99%E3%82%8A%E3%81%8C%200%20%E3%81%AE%E3%81%A8%E3%81%8D%E3%81%AF%E3%80%81%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E3%81%AB%E3%81%99%E3%82%8B%0A%20%20%20%20return%20int%28math.pow%283%2C%20a%29%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20n%20%3D%2058%0A%0A%20%20%20%20%23%20%E8%B2%AA%E6%AC%B2%E6%B3%95%0A%20%20%20%20res%20%3D%20max_product_cutting%28n%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%A4%A7%E5%88%86%E5%89%B2%E7%A9%8D%E3%81%AF%20%7Bres%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_hashing/array_hash_map.md b/ja/codes/pythontutor/chapter_hashing/array_hash_map.md new file mode 100644 index 000000000..0993ed1c8 --- /dev/null +++ b/ja/codes/pythontutor/chapter_hashing/array_hash_map.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Pair%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20self.key%20%3D%20key%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Aclass%20ArrayHashMap%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self.buckets%3A%20list%5BPair%20%7C%20None%5D%20%3D%20%5BNone%5D%20%2A%2020%0A%0A%20%20%20%20def%20hash_func%28self%2C%20key%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20index%20%3D%20key%20%25%2020%0A%20%20%20%20%20%20%20%20return%20index%0A%0A%20%20%20%20def%20get%28self%2C%20key%3A%20int%29%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20pair%3A%20Pair%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20if%20pair%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20return%20pair.val%0A%0A%20%20%20%20def%20put%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20pair%20%3D%20Pair%28key%2C%20val%29%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20self.buckets%5Bindex%5D%20%3D%20pair%0A%0A%20%20%20%20def%20remove%28self%2C%20key%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20index%3A%20int%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20self.buckets%5Bindex%5D%20%3D%20None%0A%0A%20%20%20%20def%20entry_set%28self%29%20-%3E%20list%5BPair%5D%3A%0A%20%20%20%20%20%20%20%20result%3A%20list%5BPair%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20key_set%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20result%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair.key%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20value_set%28self%29%20-%3E%20list%5Bstr%5D%3A%0A%20%20%20%20%20%20%20%20result%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result.append%28pair.val%29%0A%20%20%20%20%20%20%20%20return%20result%0A%0A%20%20%20%20def%20print%28self%29%3A%0A%20%20%20%20%20%20%20%20for%20pair%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print%28pair.key%2C%20%27-%3E%27%2C%20pair.val%29%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20hmap%20%3D%20ArrayHashMap%28%29%0A%20%20%20%20hmap.put%2812836%2C%20%27%E3%82%B7%E3%83%A3%E3%82%AA%E3%83%8F%E3%83%BC%27%29%0A%20%20%20%20hmap.put%2815937%2C%20%27%E3%82%B7%E3%83%A3%E3%82%AA%E3%83%AB%E3%82%AA%27%29%0A%20%20%20%20hmap.put%2816750%2C%20%27%E3%82%B7%E3%83%A3%E3%82%AA%E3%82%B9%E3%83%AF%E3%83%B3%27%29%0A%20%20%20%20hmap.put%2813276%2C%20%27%E3%82%B7%E3%83%A3%E3%82%AA%E3%83%95%E3%82%A1%E3%83%BC%27%29%0A%20%20%20%20hmap.put%2810583%2C%20%27%E3%82%B7%E3%83%A3%E3%82%AA%E3%83%A4%E3%83%BC%27%29%0A%20%20%20%20name%20%3D%20hmap.get%2815937%29%0A%20%20%20%20hmap.remove%2810583%29%0A%20%20%20%20print%28%27%5Cn%E3%82%AD%E3%83%BC%E3%81%A8%E5%80%A4%E3%81%AE%E3%83%9A%E3%82%A2%20Key-%3EValue%20%E3%82%92%E8%B5%B0%E6%9F%BB%27%29%0A%20%20%20%20for%20pair%20in%20hmap.entry_set%28%29%3A%0A%20%20%20%20%20%20%20%20print%28pair.key%2C%20%27-%3E%27%2C%20pair.val%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_hashing/hash_map_chaining.md b/ja/codes/pythontutor/chapter_hashing/hash_map_chaining.md new file mode 100644 index 000000000..2aa0cce7a --- /dev/null +++ b/ja/codes/pythontutor/chapter_hashing/hash_map_chaining.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20Pair%3A%0A%20%20%20%20%22%22%22%E3%82%AD%E3%83%BC%E3%81%A8%E5%80%A4%E3%81%AE%E7%B5%84%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20self.key%20%3D%20key%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%0Aclass%20HashMapChaining%3A%0A%20%20%20%20%22%22%22%E3%83%81%E3%82%A7%E3%82%A4%E3%83%B3%E6%B3%95%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%22%22%22%0A%20%20%20%20%20%20%20%20self.size%20%3D%200%0A%20%20%20%20%20%20%20%20self.capacity%20%3D%204%0A%20%20%20%20%20%20%20%20self.load_thres%20%3D%202.0%20%2F%203.0%0A%20%20%20%20%20%20%20%20self.extend_ratio%20%3D%202%0A%20%20%20%20%20%20%20%20self.buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28self.capacity%29%5D%0A%0A%20%20%20%20def%20hash_func%28self%2C%20key%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E9%96%A2%E6%95%B0%22%22%22%0A%20%20%20%20%20%20%20%20return%20key%20%25%20self.capacity%0A%0A%20%20%20%20def%20load_factor%28self%29%20-%3E%20float%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%B2%A0%E8%8D%B7%E7%8E%87%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%20%2F%20self.capacity%0A%0A%20%20%20%20def%20get%28self%2C%20key%3A%20int%29%20-%3E%20str%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E6%A4%9C%E7%B4%A2%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20pair.val%0A%20%20%20%20%20%20%20%20return%20None%0A%0A%20%20%20%20def%20put%28self%2C%20key%3A%20int%2C%20val%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%BF%BD%E5%8A%A0%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.load_factor%28%29%20%3E%20self.load_thres%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.extend%28%29%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pair.val%20%3D%20val%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20pair%20%3D%20Pair%28key%2C%20val%29%0A%20%20%20%20%20%20%20%20bucket.append%28pair%29%0A%20%20%20%20%20%20%20%20self.size%20%2B%3D%201%0A%0A%20%20%20%20def%20remove%28self%2C%20key%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%89%8A%E9%99%A4%E6%93%8D%E4%BD%9C%22%22%22%0A%20%20%20%20%20%20%20%20index%20%3D%20self.hash_func%28key%29%0A%20%20%20%20%20%20%20%20bucket%20%3D%20self.buckets%5Bindex%5D%0A%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pair.key%20%3D%3D%20key%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20bucket.remove%28pair%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.size%20-%3D%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%0A%20%20%20%20def%20extend%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E6%8B%A1%E5%BC%B5%22%22%22%0A%20%20%20%20%20%20%20%20buckets%20%3D%20self.buckets%0A%20%20%20%20%20%20%20%20self.capacity%20%2A%3D%20self.extend_ratio%0A%20%20%20%20%20%20%20%20self.buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28self.capacity%29%5D%0A%20%20%20%20%20%20%20%20self.size%20%3D%200%0A%20%20%20%20%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.put%28pair.key%2C%20pair.val%29%0A%0A%20%20%20%20def%20print%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%87%BA%E5%8A%9B%22%22%22%0A%20%20%20%20%20%20%20%20for%20bucket%20in%20self.buckets%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20pair%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20res.append%28str%28pair.key%29%20%2B%20%22%20-%3E%20%22%20%2B%20pair.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28res%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20hashmap%20%3D%20HashMapChaining%28%29%0A%0A%20%20%20%20%23%20%E8%BF%BD%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20hashmap.put%2812836%2C%20%22%E3%82%B7%E3%83%A3%E3%82%AA%E3%83%8F%E3%83%BC%22%29%0A%20%20%20%20hashmap.put%2815937%2C%20%22%E3%82%B7%E3%83%A3%E3%82%AA%E3%83%AB%E3%82%AA%22%29%0A%20%20%20%20hashmap.put%2816750%2C%20%22%E3%82%B7%E3%83%A3%E3%82%AA%E3%82%B9%E3%83%AF%E3%83%B3%22%29%0A%20%20%20%20hashmap.put%2813276%2C%20%22%E3%82%B7%E3%83%A3%E3%82%AA%E3%83%95%E3%82%A1%E3%83%BC%22%29%0A%20%20%20%20hashmap.put%2810583%2C%20%22%E3%82%B7%E3%83%A3%E3%82%AA%E3%83%A4%E3%83%BC%22%29%0A%0A%20%20%20%20%23%20%E6%A4%9C%E7%B4%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20name%20%3D%20hashmap.get%2813276%29%0A%0A%20%20%20%20%23%20%E5%89%8A%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20hashmap.remove%2812836%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_hashing/simple_hash.md b/ja/codes/pythontutor/chapter_hashing/simple_hash.md new file mode 100644 index 000000000..1eae43560 --- /dev/null +++ b/ja/codes/pythontutor/chapter_hashing/simple_hash.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20add_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%8A%A0%E7%AE%97%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%2B%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20mul_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%B9%97%E7%AE%97%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%2031%20%2A%20hash%20%2B%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20xor_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22XOR%20%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%5E%3D%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Adef%20rot_hash%28key%3A%20str%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E5%9B%9E%E8%BB%A2%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%22%22%22%0A%20%20%20%20hash%20%3D%200%0A%20%20%20%20modulus%20%3D%201000000007%0A%20%20%20%20for%20c%20in%20key%3A%0A%20%20%20%20%20%20%20%20hash%20%3D%20%28hash%20%3C%3C%204%29%20%5E%20%28hash%20%3E%3E%2028%29%20%5E%20ord%28c%29%0A%20%20%20%20return%20hash%20%25%20modulus%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20key%20%3D%20%22Hello%20%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%22%0A%0A%20%20%20%20hash%20%3D%20add_hash%28key%29%0A%20%20%20%20print%28f%22%E5%8A%A0%E7%AE%97%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E5%80%A4%E3%81%AF%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20mul_hash%28key%29%0A%20%20%20%20print%28f%22%E4%B9%97%E7%AE%97%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E5%80%A4%E3%81%AF%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20xor_hash%28key%29%0A%20%20%20%20print%28f%22XOR%20%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E5%80%A4%E3%81%AF%20%7Bhash%7D%22%29%0A%0A%20%20%20%20hash%20%3D%20rot_hash%28key%29%0A%20%20%20%20print%28f%22%E5%9B%9E%E8%BB%A2%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E5%80%A4%E3%81%AF%20%7Bhash%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_heap/my_heap.md b/ja/codes/pythontutor/chapter_heap/my_heap.md new file mode 100644 index 000000000..cf56fa076 --- /dev/null +++ b/ja/codes/pythontutor/chapter_heap/my_heap.md @@ -0,0 +1,20 @@ + + + +https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E3%83%92%E3%83%BC%E3%83%97%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%E3%80%82%E5%85%A5%E5%8A%9B%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AB%E5%9F%BA%E3%81%A5%E3%81%84%E3%81%A6%E3%83%92%E3%83%BC%E3%83%97%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E3%83%AA%E3%82%B9%E3%83%88%E8%A6%81%E7%B4%A0%E3%82%92%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E3%83%92%E3%83%BC%E3%83%97%E3%81%AB%E8%BF%BD%E5%8A%A0%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%20%20%20%20%20%20%20%20%23%20%E8%91%89%E3%83%8E%E3%83%BC%E3%83%89%E4%BB%A5%E5%A4%96%E3%81%AE%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.parent%28self.size%28%29%20-%201%29%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.sift_down%28i%29%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A6%AA%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%20%20%23%20%E5%88%87%E3%82%8A%E6%8D%A8%E3%81%A6%E9%99%A4%E7%AE%97%0A%0A%20%20%20%20def%20swap%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A6%81%E7%B4%A0%E3%82%92%E4%BA%A4%E6%8F%9B%22%22%22%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D%2C%20self.max_heap%5Bj%5D%20%3D%20self.max_heap%5Bj%5D%2C%20self.max_heap%5Bi%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%92%E3%83%BC%E3%83%97%E3%81%AE%E3%82%B5%E3%82%A4%E3%82%BA%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20sift_down%28self%2C%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%8E%E3%83%BC%E3%83%89%20i%20%E3%81%8B%E3%82%89%E5%A7%8B%E3%82%81%E3%81%A6%E3%80%81%E4%B8%8A%E3%81%8B%E3%82%89%E4%B8%8B%E3%81%B8%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%22%22%22%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%20i%2C%20l%2C%20r%20%E3%81%AE%E3%81%86%E3%81%A1%E5%80%A4%E3%81%8C%E6%9C%80%E5%A4%A7%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%20ma%20%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20l%2C%20r%2C%20ma%20%3D%20self.left%28i%29%2C%20self.right%28i%29%2C%20i%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20l%20%3C%20self.size%28%29%20and%20self.max_heap%5Bl%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20r%20%3C%20self.size%28%29%20and%20self.max_heap%5Br%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%20i%20%E3%81%8C%E6%9C%80%E5%A4%A7%E3%80%81%E3%81%BE%E3%81%9F%E3%81%AF%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20l%2C%20r%20%E3%81%8C%E7%AF%84%E5%9B%B2%E5%A4%96%E3%81%AA%E3%82%89%E3%80%81%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%E3%81%AF%E4%B8%8D%E8%A6%81%E3%81%AA%E3%81%AE%E3%81%A7%E6%8A%9C%E3%81%91%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%202%20%E3%81%A4%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i%2C%20ma%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%A7%E4%B8%8A%E3%81%8B%E3%82%89%E4%B8%8B%E3%81%B8%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E6%9C%80%E5%A4%A7%E3%83%92%E3%83%BC%E3%83%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B1%2C%202%2C%203%2C%204%2C%205%5D%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + + + + +https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E3%83%92%E3%83%BC%E3%83%97%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E3%83%AA%E3%82%B9%E3%83%88%E8%A6%81%E7%B4%A0%E3%82%92%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E3%83%92%E3%83%BC%E3%83%97%E3%81%AB%E8%BF%BD%E5%8A%A0%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A6%AA%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%20%20%23%20%E5%88%87%E3%82%8A%E6%8D%A8%E3%81%A6%E9%99%A4%E7%AE%97%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%92%E3%83%BC%E3%83%97%E3%81%AE%E3%82%B5%E3%82%A4%E3%82%BA%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%92%E3%83%BC%E3%83%97%E3%81%8C%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%E3%82%92%E5%88%A4%E5%AE%9A%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%92%E3%83%BC%E3%83%97%E5%85%88%E9%A0%AD%E8%A6%81%E7%B4%A0%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.max_heap%5B0%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E6%9C%80%E5%A4%A7%E3%83%92%E3%83%BC%E3%83%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20%23%20%E5%85%A5%E5%8A%9B%E9%85%8D%E5%88%97%E3%81%AF%E3%81%99%E3%81%A7%E3%81%AB%E6%AD%A3%E5%BD%93%E3%81%AA%E3%83%92%E3%83%BC%E3%83%97%E3%81%A7%E3%81%82%E3%82%8B%E7%82%B9%E3%81%AB%E6%B3%A8%E6%84%8F%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9%2C%208%2C%206%2C%206%2C%207%2C%205%2C%202%2C%201%2C%204%2C%203%2C%206%2C%202%5D%29%0A%0A%20%20%20%20%23%20%E3%83%92%E3%83%BC%E3%83%97%E9%A0%82%E7%82%B9%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E5%8F%96%E5%BE%97%0A%20%20%20%20peek%20%3D%20max_heap.peek%28%29%0A%20%20%20%20print%28f%22%5Cn%E3%83%92%E3%83%BC%E3%83%97%E5%85%88%E9%A0%AD%E8%A6%81%E7%B4%A0%E3%81%AF%20%7Bpeek%7D%22%29&cumulative=false&curInstr=7&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E3%83%92%E3%83%BC%E3%83%97%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E3%83%AA%E3%82%B9%E3%83%88%E8%A6%81%E7%B4%A0%E3%82%92%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E3%83%92%E3%83%BC%E3%83%97%E3%81%AB%E8%BF%BD%E5%8A%A0%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A6%AA%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%20%20%23%20%E5%88%87%E3%82%8A%E6%8D%A8%E3%81%A6%E9%99%A4%E7%AE%97%0A%0A%20%20%20%20def%20swap%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A6%81%E7%B4%A0%E3%82%92%E4%BA%A4%E6%8F%9B%22%22%22%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D%2C%20self.max_heap%5Bj%5D%20%3D%20self.max_heap%5Bj%5D%2C%20self.max_heap%5Bi%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%92%E3%83%BC%E3%83%97%E3%81%AE%E3%82%B5%E3%82%A4%E3%82%BA%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%92%E3%83%BC%E3%83%97%E3%81%8C%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%E3%82%92%E5%88%A4%E5%AE%9A%22%22%22%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20push%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A6%81%E7%B4%A0%E3%82%92%E3%83%92%E3%83%BC%E3%83%97%E3%81%AB%E8%BF%BD%E5%8A%A0%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E8%BF%BD%E5%8A%A0%0A%20%20%20%20%20%20%20%20self.max_heap.append%28val%29%0A%20%20%20%20%20%20%20%20%23%20%E4%B8%8B%E3%81%8B%E3%82%89%E4%B8%8A%E3%81%B8%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%0A%20%20%20%20%20%20%20%20self.sift_up%28self.size%28%29%20-%201%29%0A%0A%20%20%20%20def%20sift_up%28self%2C%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%8E%E3%83%BC%E3%83%89%20i%20%E3%81%8B%E3%82%89%E5%A7%8B%E3%82%81%E3%81%A6%E3%80%81%E4%B8%8B%E3%81%8B%E3%82%89%E4%B8%8A%E3%81%B8%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%22%22%22%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%20i%20%E3%81%AE%E8%A6%AA%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%8F%96%E5%BE%97%0A%20%20%20%20%20%20%20%20%20%20%20%20p%20%3D%20self.parent%28i%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%80%8C%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E8%B6%8A%E3%81%88%E3%81%9F%E3%80%8D%E3%81%BE%E3%81%9F%E3%81%AF%E3%80%8C%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E4%BF%AE%E5%BE%A9%E3%81%8C%E4%B8%8D%E8%A6%81%E3%80%8D%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%9F%E3%82%89%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%E3%82%92%E7%B5%82%E4%BA%86%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20p%20%3C%200%20or%20self.max_heap%5Bi%5D%20%3C%3D%20self.max_heap%5Bp%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%202%20%E3%81%A4%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i%2C%20p%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%A7%E4%B8%8B%E3%81%8B%E3%82%89%E4%B8%8A%E3%81%B8%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20p%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E6%9C%80%E5%A4%A7%E3%83%92%E3%83%BC%E3%83%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20%23%20%E5%85%A5%E5%8A%9B%E9%85%8D%E5%88%97%E3%81%AF%E3%81%99%E3%81%A7%E3%81%AB%E6%AD%A3%E5%BD%93%E3%81%AA%E3%83%92%E3%83%BC%E3%83%97%E3%81%A7%E3%81%82%E3%82%8B%E7%82%B9%E3%81%AB%E6%B3%A8%E6%84%8F%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9%2C%208%2C%206%2C%206%2C%207%2C%205%2C%202%2C%201%2C%204%2C%203%2C%206%2C%202%5D%29%0A%0A%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%82%92%E3%83%92%E3%83%BC%E3%83%97%E3%81%AB%E8%BF%BD%E5%8A%A0%0A%20%20%20%20val%20%3D%207%0A%20%20%20%20max_heap.push%28val%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20MaxHeap%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%20%20%20%20self.max_heap%20%3D%20nums%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%0A%0A%20%20%20%20def%20swap%28self%2C%20i%3A%20int%2C%20j%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.max_heap%5Bi%5D%2C%20self.max_heap%5Bj%5D%20%3D%20%28self.max_heap%5Bj%5D%2C%20self.max_heap%5Bi%5D%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20len%28self.max_heap%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20return%20self.size%28%29%20%3D%3D%200%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%E3%83%92%E3%83%BC%E3%83%97%E3%81%8C%E7%A9%BA%E3%81%A7%E3%81%99%27%29%0A%20%20%20%20%20%20%20%20self.swap%280%2C%20self.size%28%29%20-%201%29%0A%20%20%20%20%20%20%20%20val%20%3D%20self.max_heap.pop%28%29%0A%20%20%20%20%20%20%20%20self.sift_down%280%29%0A%20%20%20%20%20%20%20%20return%20val%0A%0A%20%20%20%20def%20sift_down%28self%2C%20i%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20l%2C%20r%2C%20ma%20%3D%20%28self.left%28i%29%2C%20self.right%28i%29%2C%20i%29%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20l%20%3C%20self.size%28%29%20and%20self.max_heap%5Bl%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20r%20%3C%20self.size%28%29%20and%20self.max_heap%5Br%5D%20%3E%20self.max_heap%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20self.swap%28i%2C%20ma%29%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20ma%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20max_heap%20%3D%20MaxHeap%28%5B9%2C%208%2C%207%2C%206%2C%207%2C%206%2C%202%2C%201%2C%204%2C%203%2C%206%2C%202%2C%205%5D%29%0A%20%20%20%20peek%20%3D%20max_heap.pop%28%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_heap/top_k.md b/ja/codes/pythontutor/chapter_heap/top_k.md new file mode 100644 index 000000000..f1d03dbee --- /dev/null +++ b/ja/codes/pythontutor/chapter_heap/top_k.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=import%20heapq%0A%0Adef%20top_k_heap%28nums%3A%20list%5Bint%5D%2C%20k%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E3%83%92%E3%83%BC%E3%83%97%E3%81%AB%E5%9F%BA%E3%81%A5%E3%81%84%E3%81%A6%E9%85%8D%E5%88%97%E4%B8%AD%E3%81%AE%E6%9C%80%E5%A4%A7%E3%81%AE%20k%20%E5%80%8B%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E3%81%99%22%22%22%0A%20%20%20%20%23%20%E6%9C%80%E5%B0%8F%E3%83%92%E3%83%BC%E3%83%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20heap%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E9%85%8D%E5%88%97%E3%81%AE%E5%85%88%E9%A0%AD%20k%20%E5%80%8B%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E3%83%92%E3%83%BC%E3%83%97%E3%81%AB%E8%BF%BD%E5%8A%A0%0A%20%20%20%20for%20i%20in%20range%28k%29%3A%0A%20%20%20%20%20%20%20%20heapq.heappush%28heap%2C%20nums%5Bi%5D%29%0A%20%20%20%20%23%20k%2B1%20%E7%95%AA%E7%9B%AE%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%8B%E3%82%89%E9%96%8B%E5%A7%8B%E3%81%97%E3%80%81%E3%83%92%E3%83%BC%E3%83%97%E9%95%B7%E3%82%92%20k%20%E3%81%AB%E4%BF%9D%E3%81%A4%0A%20%20%20%20for%20i%20in%20range%28k%2C%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%8F%BE%E5%9C%A8%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%8C%E3%83%92%E3%83%BC%E3%83%97%E5%85%88%E9%A0%AD%E3%82%88%E3%82%8A%E5%A4%A7%E3%81%8D%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E3%83%92%E3%83%BC%E3%83%97%E5%85%88%E9%A0%AD%E3%82%92%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%97%E3%81%A6%E7%8F%BE%E5%9C%A8%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3E%20heap%5B0%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappop%28heap%29%0A%20%20%20%20%20%20%20%20%20%20%20%20heapq.heappush%28heap%2C%20nums%5Bi%5D%29%0A%20%20%20%20return%20heap%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%207%2C%206%2C%203%2C%202%5D%0A%20%20%20%20k%20%3D%203%0A%0A%20%20%20%20res%20%3D%20top_k_heap%28nums%2C%20k%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_searching/binary_search.md b/ja/codes/pythontutor/chapter_searching/binary_search.md new file mode 100644 index 000000000..e4ff1e05f --- /dev/null +++ b/ja/codes/pythontutor/chapter_searching/binary_search.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20binary_search%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%EF%BC%88%E4%B8%A1%E9%96%89%E5%8C%BA%E9%96%93%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E4%B8%A1%E9%96%89%E5%8C%BA%E9%96%93%20%5B0%2C%20n-1%5D%20%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%E3%80%82%E3%81%A4%E3%81%BE%E3%82%8A%20i%2C%20j%20%E3%81%AF%E3%81%9D%E3%82%8C%E3%81%9E%E3%82%8C%E9%85%8D%E5%88%97%E3%81%AE%E5%85%88%E9%A0%AD%E8%A6%81%E7%B4%A0%E3%81%A8%E6%9C%AB%E5%B0%BE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8C%87%E3%81%99%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%0A%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%97%E3%80%81%E6%8E%A2%E7%B4%A2%E5%8C%BA%E9%96%93%E3%81%8C%E7%A9%BA%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%9F%E3%82%89%E7%B5%82%E4%BA%86%E3%81%99%E3%82%8B%EF%BC%88i%20%3E%20j%20%E3%81%A7%E7%A9%BA%EF%BC%89%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%90%86%E8%AB%96%E4%B8%8A%E3%80%81Python%20%E3%81%AE%E6%95%B0%E5%80%A4%E3%81%AF%E7%84%A1%E9%99%90%E3%81%AB%E5%A4%A7%E3%81%8D%E3%81%8F%E3%81%A7%E3%81%8D%E3%82%8B%E3%81%9F%E3%82%81%EF%BC%88%E3%83%A1%E3%83%A2%E3%83%AA%E5%AE%B9%E9%87%8F%E3%81%AB%E4%BE%9D%E5%AD%98%EF%BC%89%E3%80%81%E5%A4%A7%E3%81%8D%E3%81%AA%E6%95%B0%E3%81%AE%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%95%E3%83%AD%E3%83%BC%E3%82%92%E8%80%83%E6%85%AE%E3%81%99%E3%82%8B%E5%BF%85%E8%A6%81%E3%81%AF%E3%81%AA%E3%81%84%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%E4%B8%AD%E7%82%B9%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20m%20%E3%82%92%E8%A8%88%E7%AE%97%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20%E3%81%93%E3%81%AE%E5%A0%B4%E5%90%88%E3%80%81target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bm%2B1%2C%20j%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20%E3%81%93%E3%81%AE%E5%A0%B4%E5%90%88%E3%80%81target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bi%2C%20m-1%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%E7%9B%AE%E6%A8%99%E8%A6%81%E7%B4%A0%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E3%82%89%E3%81%9D%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20-1%20%20%23%20%E7%9B%AE%E6%A8%99%E8%A6%81%E7%B4%A0%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%82%89%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%20-1%20%E3%82%92%E8%BF%94%E3%81%99%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%EF%BC%88%E4%B8%A1%E9%96%89%E5%8C%BA%E9%96%93%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search%28nums%2C%20target%29%0A%20%20%20%20print%28%22%E5%AF%BE%E8%B1%A1%E8%A6%81%E7%B4%A0%206%20%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20%3D%20%22%2C%20index%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20binary_search_lcro%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%EF%BC%88%E5%B7%A6%E9%96%89%E5%8F%B3%E9%96%8B%E5%8C%BA%E9%96%93%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E5%B7%A6%E9%96%89%E5%8F%B3%E9%96%8B%E5%8C%BA%E9%96%93%20%5B0%2C%20n%29%20%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%E3%80%82%E3%81%A4%E3%81%BE%E3%82%8A%20i%2C%20j%20%E3%81%AF%E3%81%9D%E3%82%8C%E3%81%9E%E3%82%8C%E9%85%8D%E5%88%97%E3%81%AE%E5%85%88%E9%A0%AD%E8%A6%81%E7%B4%A0%E3%81%A8%E6%9C%AB%E5%B0%BE%E8%A6%81%E7%B4%A0%2B1%E3%82%92%E6%8C%87%E3%81%99%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%0A%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%97%E3%80%81%E6%8E%A2%E7%B4%A2%E5%8C%BA%E9%96%93%E3%81%8C%E7%A9%BA%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%9F%E3%82%89%E7%B5%82%E4%BA%86%E3%81%99%E3%82%8B%EF%BC%88i%20%3D%20j%20%E3%81%A7%E7%A9%BA%EF%BC%89%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%E4%B8%AD%E7%82%B9%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20m%20%E3%82%92%E8%A8%88%E7%AE%97%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20%E3%81%93%E3%81%AE%E5%A0%B4%E5%90%88%E3%80%81target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bm%2B1%2C%20j%29%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20%20%23%20%E3%81%93%E3%81%AE%E5%A0%B4%E5%90%88%E3%80%81target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bi%2C%20m%29%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20%E7%9B%AE%E6%A8%99%E8%A6%81%E7%B4%A0%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E3%82%89%E3%81%9D%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20-1%20%20%23%20%E7%9B%AE%E6%A8%99%E8%A6%81%E7%B4%A0%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%82%89%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%20-1%20%E3%82%92%E8%BF%94%E3%81%99%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%EF%BC%88%E5%B7%A6%E9%96%89%E5%8F%B3%E9%96%8B%E5%8C%BA%E9%96%93%EF%BC%89%0A%20%20%20%20index%20%3D%20binary_search_lcro%28nums%2C%20target%29%0A%20%20%20%20print%28%22%E5%AF%BE%E8%B1%A1%E8%A6%81%E7%B4%A0%206%20%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20%3D%20%22%2C%20index%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_searching/binary_search_edge.md b/ja/codes/pythontutor/chapter_searching/binary_search_edge.md new file mode 100644 index 000000000..51e6b407b --- /dev/null +++ b/ja/codes/pythontutor/chapter_searching/binary_search_edge.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E3%81%A7%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%82%92%E6%8E%A2%E3%81%99%EF%BC%88%E9%87%8D%E8%A4%87%E8%A6%81%E7%B4%A0%E3%81%82%E3%82%8A%EF%BC%89%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%E4%B8%A1%E9%96%89%E5%8C%BA%E9%96%93%20%5B0%2C%20n-1%5D%20%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%E4%B8%AD%E7%82%B9%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20m%20%E3%82%92%E8%A8%88%E7%AE%97%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bm%2B1%2C%20j%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bi%2C%20m-1%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E3%82%88%E3%82%8A%E5%B0%8F%E3%81%95%E3%81%84%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bi%2C%20m-1%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%23%20%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%20i%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20i%0A%0Adef%20binary_search_left_edge%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E3%82%82%E5%B7%A6%E3%81%AE%20target%20%E3%82%92%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20target%20%E3%81%AE%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%82%92%E6%8E%A2%E3%81%99%E3%81%AE%E3%81%A8%E7%AD%89%E4%BE%A1%0A%20%20%20%20i%20%3D%20binary_search_insertion%28nums%2C%20target%29%0A%20%20%20%20%23%20target%20%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%82%89%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81-1%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3D%3D%20len%28nums%29%20or%20nums%5Bi%5D%20%21%3D%20target%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20target%20%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E3%82%89%E3%80%81%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20i%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20i%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%87%8D%E8%A4%87%E8%A6%81%E7%B4%A0%E3%82%92%E5%90%AB%E3%82%80%E9%85%8D%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%206%2C%206%2C%206%2C%206%2C%2010%2C%2012%2C%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E3%81%A7%E5%B7%A6%E7%AB%AF%E3%81%A8%E5%8F%B3%E7%AB%AF%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_left_edge%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%E5%B7%A6%E7%AB%AF%E3%81%AE%E8%A6%81%E7%B4%A0%20%7Btarget%7D%20%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AF%20%7Bindex%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E3%81%A7%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%82%92%E6%8E%A2%E3%81%99%EF%BC%88%E9%87%8D%E8%A4%87%E8%A6%81%E7%B4%A0%E3%81%82%E3%82%8A%EF%BC%89%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%E4%B8%A1%E9%96%89%E5%8C%BA%E9%96%93%20%5B0%2C%20n-1%5D%20%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%E4%B8%AD%E7%82%B9%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20m%20%E3%82%92%E8%A8%88%E7%AE%97%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bm%2B1%2C%20j%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bi%2C%20m-1%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E3%82%88%E3%82%8A%E5%B0%8F%E3%81%95%E3%81%84%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bi%2C%20m-1%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%23%20%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%20i%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20i%0A%0Adef%20binary_search_right_edge%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E3%82%82%E5%8F%B3%E3%81%AE%20target%20%E3%82%92%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%23%20%E6%9C%80%E5%B7%A6%E3%81%AE%20target%20%2B%201%20%E3%82%92%E6%8E%A2%E3%81%99%E5%95%8F%E9%A1%8C%E3%81%AB%E5%A4%89%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20i%20%3D%20binary_search_insertion%28nums%2C%20target%20%2B%201%29%0A%20%20%20%20%23%20j%20%E3%81%AF%E6%9C%80%E3%82%82%E5%8F%B3%E3%81%AE%20target%20%E3%82%92%E6%8C%87%E3%81%97%E3%80%81i%20%E3%81%AF%20target%20%E3%82%88%E3%82%8A%E5%A4%A7%E3%81%8D%E3%81%84%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8C%87%E3%81%99%0A%20%20%20%20j%20%3D%20i%20-%201%0A%20%20%20%20%23%20target%20%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%82%89%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81-1%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20j%20%3D%3D%20-1%20or%20nums%5Bj%5D%20%21%3D%20target%3A%0A%20%20%20%20%20%20%20%20return%20-1%0A%20%20%20%20%23%20target%20%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E3%82%89%E3%80%81%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20j%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20j%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%87%8D%E8%A4%87%E8%A6%81%E7%B4%A0%E3%82%92%E5%90%AB%E3%82%80%E9%85%8D%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%206%2C%206%2C%206%2C%206%2C%2010%2C%2012%2C%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E3%81%A7%E5%B7%A6%E7%AB%AF%E3%81%A8%E5%8F%B3%E7%AB%AF%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_right_edge%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%E5%8F%B3%E7%AB%AF%E3%81%AE%E8%A6%81%E7%B4%A0%20%7Btarget%7D%20%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AF%20%7Bindex%7D%22%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_searching/binary_search_insertion.md b/ja/codes/pythontutor/chapter_searching/binary_search_insertion.md new file mode 100644 index 000000000..fab024dcf --- /dev/null +++ b/ja/codes/pythontutor/chapter_searching/binary_search_insertion.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20binary_search_insertion_simple%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E3%81%A7%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%82%92%E6%8E%A2%E3%81%99%EF%BC%88%E9%87%8D%E8%A4%87%E8%A6%81%E7%B4%A0%E3%81%AA%E3%81%97%EF%BC%89%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%E4%B8%A1%E9%96%89%E5%8C%BA%E9%96%93%20%5B0%2C%20n-1%5D%20%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%E4%B8%AD%E7%82%B9%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20m%20%E3%82%92%E8%A8%88%E7%AE%97%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bm%2B1%2C%20j%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bi%2C%20m-1%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m%20%20%23%20target%20%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E3%82%89%E3%80%81%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%20m%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20%23%20target%20%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%82%89%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%E3%80%81%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%20i%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20i%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%87%8D%E8%A4%87%E8%A6%81%E7%B4%A0%E3%81%AE%E3%81%AA%E3%81%84%E9%85%8D%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%208%2C%2012%2C%2015%2C%2023%2C%2026%2C%2031%2C%2035%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E3%81%A7%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_insertion_simple%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%E8%A6%81%E7%B4%A0%20%7Btarget%7D%20%E3%81%AE%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AF%20%7Bindex%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20binary_search_insertion%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E3%81%A7%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%82%92%E6%8E%A2%E3%81%99%EF%BC%88%E9%87%8D%E8%A4%87%E8%A6%81%E7%B4%A0%E3%81%82%E3%82%8A%EF%BC%89%22%22%22%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28nums%29%20-%201%20%20%23%20%E4%B8%A1%E9%96%89%E5%8C%BA%E9%96%93%20%5B0%2C%20n-1%5D%20%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20while%20i%20%3C%3D%20j%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20%28i%20%2B%20j%29%20%2F%2F%202%20%20%23%20%E4%B8%AD%E7%82%B9%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20m%20%E3%82%92%E8%A8%88%E7%AE%97%0A%20%20%20%20%20%20%20%20if%20nums%5Bm%5D%20%3C%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%3D%20m%20%2B%201%20%20%23%20target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bm%2B1%2C%20j%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20elif%20nums%5Bm%5D%20%3E%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bi%2C%20m-1%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%3D%20m%20-%201%20%20%23%20target%20%E3%82%88%E3%82%8A%E5%B0%8F%E3%81%95%E3%81%84%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%AF%E5%8C%BA%E9%96%93%20%5Bi%2C%20m-1%5D%20%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%23%20%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%20i%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20return%20i%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E9%87%8D%E8%A4%87%E8%A6%81%E7%B4%A0%E3%82%92%E5%90%AB%E3%82%80%E9%85%8D%E5%88%97%0A%20%20%20%20nums%20%3D%20%5B1%2C%203%2C%206%2C%206%2C%206%2C%206%2C%206%2C%2010%2C%2012%2C%2015%5D%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E3%81%A7%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20target%20%3D%206%0A%20%20%20%20index%20%3D%20binary_search_insertion%28nums%2C%20target%29%0A%20%20%20%20print%28f%22%E8%A6%81%E7%B4%A0%20%7Btarget%7D%20%E3%81%AE%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AF%20%7Bindex%7D%22%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_searching/two_sum.md b/ja/codes/pythontutor/chapter_searching/two_sum.md new file mode 100644 index 000000000..9fc65f61a --- /dev/null +++ b/ja/codes/pythontutor/chapter_searching/two_sum.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20two_sum_brute_force%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E6%96%B9%E6%B3%95%201%EF%BC%9A%E7%B7%8F%E5%BD%93%E3%81%9F%E3%82%8A%E5%88%97%E6%8C%99%22%22%22%0A%20%20%20%20%23%202%E9%87%8D%E3%83%AB%E3%83%BC%E3%83%97%E3%81%AE%E3%81%9F%E3%82%81%E3%80%81%E6%99%82%E9%96%93%E8%A8%88%E7%AE%97%E9%87%8F%E3%81%AF%20O%28n%5E2%29%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%29%3A%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%20%2B%201%2C%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%2B%20nums%5Bj%5D%20%3D%3D%20target%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20%5Bi%2C%20j%5D%0A%20%20%20%20return%20%5B%5D%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2%2C%207%2C%2011%2C%2015%5D%0A%20%20%20%20target%20%3D%2013%0A%20%20%20%20res%20%3D%20two_sum_brute_force%28nums%2C%20target%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20two_sum_hash_table%28nums%3A%20list%5Bint%5D%2C%20target%3A%20int%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E6%96%B9%E6%B3%95%202%EF%BC%9A%E8%A3%9C%E5%8A%A9%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%22%22%22%0A%20%20%20%20%23%20%E8%A3%9C%E5%8A%A9%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%80%81%E7%A9%BA%E9%96%93%E8%A8%88%E7%AE%97%E9%87%8F%E3%81%AF%20O%28n%29%0A%20%20%20%20dic%20%3D%20%7B%7D%0A%20%20%20%20%23%20%E5%8D%98%E4%B8%80%E3%83%AB%E3%83%BC%E3%83%97%E3%81%A7%E3%80%81%E6%99%82%E9%96%93%E8%A8%88%E7%AE%97%E9%87%8F%E3%81%AF%20O%28n%29%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20if%20target%20-%20nums%5Bi%5D%20in%20dic%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%5Bdic%5Btarget%20-%20nums%5Bi%5D%5D%2C%20i%5D%0A%20%20%20%20%20%20%20%20dic%5Bnums%5Bi%5D%5D%20%3D%20i%0A%20%20%20%20return%20%5B%5D%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2%2C%207%2C%2011%2C%2015%5D%0A%20%20%20%20target%20%3D%2013%0A%20%20%20%20res%20%3D%20two_sum_hash_table%28nums%2C%20target%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_sorting/bubble_sort.md b/ja/codes/pythontutor/chapter_sorting/bubble_sort.md new file mode 100644 index 000000000..68fcc6468 --- /dev/null +++ b/ja/codes/pythontutor/chapter_sorting/bubble_sort.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E3%83%90%E3%83%96%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%E3%81%AF%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%20%5B0%2C%20i%5D%20%E3%81%AE%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%E3%82%92%E3%81%9D%E3%81%AE%E5%8C%BA%E9%96%93%E3%81%AE%E6%9C%80%E5%8F%B3%E7%AB%AF%E3%81%B8%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20nums%5Bj%5D%20%E3%81%A8%20nums%5Bj%20%2B%201%5D%20%E3%82%92%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%2C%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D%2C%20nums%5Bj%5D%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%E3%83%90%E3%83%96%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20bubble_sort_with_flag%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E3%83%90%E3%83%96%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%88%E3%83%95%E3%83%A9%E3%82%B0%E6%9C%80%E9%81%A9%E5%8C%96%EF%BC%89%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%E3%81%AF%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20flag%20%3D%20False%20%20%23%20%E3%83%95%E3%83%A9%E3%82%B0%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%20%5B0%2C%20i%5D%20%E3%81%AE%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%E3%82%92%E3%81%9D%E3%81%AE%E5%8C%BA%E9%96%93%E3%81%AE%E6%9C%80%E5%8F%B3%E7%AB%AF%E3%81%B8%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20nums%5Bj%5D%20%E3%81%A8%20nums%5Bj%20%2B%201%5D%20%E3%82%92%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%2C%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D%2C%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20flag%20%3D%20True%20%20%23%20%E4%BA%A4%E6%8F%9B%E3%81%99%E3%82%8B%E8%A6%81%E7%B4%A0%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20%20%20%20%20if%20not%20flag%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%20%20%23%20%E3%81%93%E3%81%AE%E3%83%90%E3%83%96%E3%83%AB%E5%87%A6%E7%90%86%E3%81%A7%E8%A6%81%E7%B4%A0%E4%BA%A4%E6%8F%9B%E3%81%8C%E4%B8%80%E5%BA%A6%E3%82%82%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E7%B5%82%E4%BA%86%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20bubble_sort_with_flag%28nums%29%0A%20%20%20%20print%28%22%E3%83%90%E3%83%96%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_sorting/bucket_sort.md b/ja/codes/pythontutor/chapter_sorting/bucket_sort.md new file mode 100644 index 000000000..a0fa38bb9 --- /dev/null +++ b/ja/codes/pythontutor/chapter_sorting/bucket_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20bucket_sort%28nums%3A%20list%5Bfloat%5D%29%3A%0A%20%20%20%20%22%22%22%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20k%20%3D%20n%2F2%20%E5%80%8B%E3%81%AE%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%97%E3%80%81%E5%90%84%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%81%AB%202%20%E8%A6%81%E7%B4%A0%E3%81%9A%E3%81%A4%E5%89%B2%E3%82%8A%E5%BD%93%E3%81%A6%E3%82%8B%E6%83%B3%E5%AE%9A%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20k%20%3D%20len%28nums%29%20%2F%2F%202%0A%20%20%20%20buckets%20%3D%20%5B%5B%5D%20for%20_%20in%20range%28k%29%5D%0A%20%20%20%20%23%201.%20%E9%85%8D%E5%88%97%E8%A6%81%E7%B4%A0%E3%82%92%E5%90%84%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%81%AB%E6%8C%AF%E3%82%8A%E5%88%86%E3%81%91%E3%82%8B%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E7%AF%84%E5%9B%B2%E3%81%AF%20%5B0%2C%201%29%20%E3%81%A7%E3%81%82%E3%82%8A%E3%80%81num%20%2A%20k%20%E3%82%92%E7%94%A8%E3%81%84%E3%81%A6%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E7%AF%84%E5%9B%B2%20%5B0%2C%20k-1%5D%20%E3%81%AB%E5%86%99%E5%83%8F%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20i%20%3D%20int%28num%20%2A%20k%29%0A%20%20%20%20%20%20%20%20%23%20num%20%E3%82%92%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%20i%20%E3%81%AB%E8%BF%BD%E5%8A%A0%0A%20%20%20%20%20%20%20%20buckets%5Bi%5D.append%28num%29%0A%20%20%20%20%23%202.%20%E5%90%84%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%92%E3%82%BD%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E3%81%AE%E3%82%BD%E3%83%BC%E3%83%88%E9%96%A2%E6%95%B0%E3%82%92%E4%BD%BF%E3%81%86%E3%80%82%E4%BB%96%E3%81%AE%E3%82%BD%E3%83%BC%E3%83%88%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0%E3%81%AB%E7%BD%AE%E3%81%8D%E6%8F%9B%E3%81%88%E3%81%A6%E3%82%82%E3%82%88%E3%81%84%0A%20%20%20%20%20%20%20%20bucket.sort%28%29%0A%20%20%20%20%23%203.%20%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%92%E8%B5%B0%E6%9F%BB%E3%81%97%E3%81%A6%E7%B5%90%E6%9E%9C%E3%82%92%E7%B5%90%E5%90%88%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20bucket%20in%20buckets%3A%0A%20%20%20%20%20%20%20%20for%20num%20in%20bucket%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%85%A5%E5%8A%9B%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AF%E7%AF%84%E5%9B%B2%20%5B0%2C%201%29%20%E3%81%AE%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20nums%20%3D%20%5B0.49%2C%200.96%2C%200.82%2C%200.09%2C%200.57%2C%200.43%2C%200.91%2C%200.75%2C%200.15%2C%200.37%5D%0A%20%20%20%20bucket_sort%28nums%29%0A%20%20%20%20print%28%22%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_sorting/counting_sort.md b/ja/codes/pythontutor/chapter_sorting/counting_sort.md new file mode 100644 index 000000000..0bdffdb33 --- /dev/null +++ b/ja/codes/pythontutor/chapter_sorting/counting_sort.md @@ -0,0 +1,11 @@ + + + +https://pythontutor.com/render.html#code=def%20counting_sort_naive%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E8%A8%88%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E7%B0%A1%E6%98%93%E7%89%88%E3%80%82%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E3%82%BD%E3%83%BC%E3%83%88%E3%81%AB%E3%81%AF%E4%BD%BF%E3%81%88%E3%81%AA%E3%81%84%0A%20%20%20%20%23%201.%20%E9%85%8D%E5%88%97%E3%81%AE%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%20m%20%E3%82%92%E6%B1%82%E3%82%81%E3%82%8B%0A%20%20%20%20m%20%3D%200%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20m%20%3D%20max%28m%2C%20num%29%0A%20%20%20%20%23%202.%20%E5%90%84%E6%95%B0%E5%80%A4%E3%81%AE%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%82%92%E6%95%B0%E3%81%88%E3%82%8B%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E3%81%AF%20num%20%E3%81%AE%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%82%92%E8%A1%A8%E3%81%99%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20counter%20%E3%82%92%E8%B5%B0%E6%9F%BB%E3%81%97%E3%80%81%E5%90%84%E8%A6%81%E7%B4%A0%E3%82%92%E5%85%83%E3%81%AE%E9%85%8D%E5%88%97%20nums%20%E3%81%AB%E6%9B%B8%E3%81%8D%E6%88%BB%E3%81%99%0A%20%20%20%20i%20%3D%200%0A%20%20%20%20for%20num%20in%20range%28m%20%2B%201%29%3A%0A%20%20%20%20%20%20%20%20for%20_%20in%20range%28counter%5Bnum%5D%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%200%2C%201%2C%202%2C%200%2C%204%2C%200%2C%202%2C%202%2C%204%5D%0A%20%20%20%20counting_sort_naive%28nums%29%0A%20%20%20%20print%28f%22%E8%A8%88%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%88%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AF%E3%82%BD%E3%83%BC%E3%83%88%E4%B8%8D%E5%8F%AF%EF%BC%89%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%20%7Bnums%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20counting_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E8%A8%88%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E5%AE%8C%E5%85%A8%E7%89%88%E3%80%82%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E3%82%BD%E3%83%BC%E3%83%88%E3%81%A7%E3%81%8D%E3%80%81%E3%81%8B%E3%81%A4%E5%AE%89%E5%AE%9A%E3%82%BD%E3%83%BC%E3%83%88%E3%81%A7%E3%81%82%E3%82%8B%0A%20%20%20%20%23%201.%20%E9%85%8D%E5%88%97%E3%81%AE%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%20m%20%E3%82%92%E6%B1%82%E3%82%81%E3%82%8B%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%202.%20%E5%90%84%E6%95%B0%E5%80%A4%E3%81%AE%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%82%92%E6%95%B0%E3%81%88%E3%82%8B%0A%20%20%20%20%23%20counter%5Bnum%5D%20%E3%81%AF%20num%20%E3%81%AE%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%82%92%E8%A1%A8%E3%81%99%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%20%28m%20%2B%201%29%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20%2B%3D%201%0A%20%20%20%20%23%203.%20counter%20%E3%81%AE%E7%B4%AF%E7%A9%8D%E5%92%8C%E3%82%92%E6%B1%82%E3%82%81%E3%81%A6%E3%80%81%E3%80%8C%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%80%8D%E3%82%92%E3%80%8C%E6%9C%AB%E5%B0%BE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%80%8D%E3%81%AB%E5%A4%89%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E3%81%A4%E3%81%BE%E3%82%8A%20counter%5Bnum%5D-1%20%E3%81%AF%E3%80%81num%20%E3%81%8C%20res%20%E3%81%AB%E6%9C%80%E5%BE%8C%E3%81%AB%E7%8F%BE%E3%82%8C%E3%82%8B%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%0A%20%20%20%20for%20i%20in%20range%28m%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%20%2B%201%5D%20%2B%3D%20counter%5Bi%5D%0A%20%20%20%20%23%204.%20nums%20%E3%82%92%E9%80%86%E9%A0%86%E3%81%AB%E8%B5%B0%E6%9F%BB%E3%81%97%E3%80%81%E5%90%84%E8%A6%81%E7%B4%A0%E3%82%92%E7%B5%90%E6%9E%9C%E9%85%8D%E5%88%97%20res%20%E3%81%AB%E6%A0%BC%E7%B4%8D%E3%81%99%E3%82%8B%0A%20%20%20%20%23%20%E7%B5%90%E6%9E%9C%E3%82%92%E8%A8%98%E9%8C%B2%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E9%85%8D%E5%88%97%20res%20%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20num%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20res%5Bcounter%5Bnum%5D%20-%201%5D%20%3D%20num%20%20%23%20num%20%E3%82%92%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AB%E9%85%8D%E7%BD%AE%0A%20%20%20%20%20%20%20%20counter%5Bnum%5D%20-%3D%201%20%20%23%20%E7%B4%AF%E7%A9%8D%E5%92%8C%E3%82%92%201%20%E6%B8%9B%E3%82%89%E3%81%97%E3%81%A6%E3%80%81%E6%AC%A1%E3%81%AB%20num%20%E3%82%92%E9%85%8D%E7%BD%AE%E3%81%99%E3%82%8B%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%BE%97%E3%82%8B%0A%20%20%20%20%23%20%E7%B5%90%E6%9E%9C%E9%85%8D%E5%88%97%20res%20%E3%81%A7%E5%85%83%E3%81%AE%E9%85%8D%E5%88%97%20nums%20%E3%82%92%E4%B8%8A%E6%9B%B8%E3%81%8D%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B1%2C%200%2C%201%2C%202%2C%200%2C%204%2C%200%2C%202%2C%202%2C%204%5D%0A%20%20%20%20counting_sort%28nums%29%0A%20%20%20%20print%28f%22%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%20%7Bnums%7D%22%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_sorting/heap_sort.md b/ja/codes/pythontutor/chapter_sorting/heap_sort.md new file mode 100644 index 000000000..fbc37edad --- /dev/null +++ b/ja/codes/pythontutor/chapter_sorting/heap_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20sift_down%28nums%3A%20list%5Bint%5D%2C%20n%3A%20int%2C%20i%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E3%83%92%E3%83%BC%E3%83%97%E3%81%AE%E9%95%B7%E3%81%95%E3%81%AF%20n%E3%80%82%E3%83%8E%E3%83%BC%E3%83%89%20i%20%E3%81%8B%E3%82%89%E4%B8%8B%E6%96%B9%E5%90%91%E3%81%AB%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%22%22%22%0A%20%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%20i%2C%20l%2C%20r%20%E3%81%AE%E3%81%86%E3%81%A1%E5%80%A4%E3%81%8C%E6%9C%80%E5%A4%A7%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%20ma%20%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20l%20%3D%202%20%2A%20i%20%2B%201%0A%20%20%20%20%20%20%20%20r%20%3D%202%20%2A%20i%20%2B%202%0A%20%20%20%20%20%20%20%20ma%20%3D%20i%0A%20%20%20%20%20%20%20%20if%20l%20%3C%20n%20and%20nums%5Bl%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20l%0A%20%20%20%20%20%20%20%20if%20r%20%3C%20n%20and%20nums%5Br%5D%20%3E%20nums%5Bma%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ma%20%3D%20r%0A%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%20i%20%E3%81%8C%E6%9C%80%E5%A4%A7%E3%80%81%E3%81%BE%E3%81%9F%E3%81%AF%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20l%2C%20r%20%E3%81%8C%E7%AF%84%E5%9B%B2%E5%A4%96%E3%81%AA%E3%82%89%E3%80%81%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%E3%81%AF%E4%B8%8D%E8%A6%81%E3%81%AA%E3%81%AE%E3%81%A7%E6%8A%9C%E3%81%91%E3%82%8B%0A%20%20%20%20%20%20%20%20if%20ma%20%3D%3D%20i%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%23%202%20%E3%81%A4%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bma%5D%20%3D%20nums%5Bma%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%A7%E4%B8%8A%E3%81%8B%E3%82%89%E4%B8%8B%E3%81%B8%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%0A%20%20%20%20%20%20%20%20i%20%3D%20ma%0A%0Adef%20heap_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E3%83%92%E3%83%BC%E3%83%97%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E3%83%92%E3%83%BC%E3%83%97%E6%A7%8B%E7%AF%89%EF%BC%9A%E8%91%89%E3%83%8E%E3%83%BC%E3%83%89%E4%BB%A5%E5%A4%96%E3%81%AE%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20%2F%2F%202%20-%201%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20sift_down%28nums%2C%20len%28nums%29%2C%20i%29%0A%20%20%20%20%23%20%E3%83%92%E3%83%BC%E3%83%97%E3%81%8B%E3%82%89%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%E3%82%92%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%97%E3%80%81n-1%20%E5%9B%9E%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%99%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%E3%81%A8%E6%9C%80%E3%82%82%E5%8F%B3%E3%81%AE%E8%91%89%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E4%BA%A4%E6%8F%9B%EF%BC%88%E5%85%88%E9%A0%AD%E8%A6%81%E7%B4%A0%E3%81%A8%E6%9C%AB%E5%B0%BE%E8%A6%81%E7%B4%A0%E3%82%92%E4%BA%A4%E6%8F%9B%EF%BC%89%0A%20%20%20%20%20%20%20%20nums%5B0%5D%2C%20nums%5Bi%5D%20%3D%20nums%5Bi%5D%2C%20nums%5B0%5D%0A%20%20%20%20%20%20%20%20%23%20%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E8%B5%B7%E7%82%B9%E3%81%AB%E3%80%81%E4%B8%8A%E3%81%8B%E3%82%89%E4%B8%8B%E3%81%B8%E3%83%92%E3%83%BC%E3%83%97%E5%8C%96%0A%20%20%20%20%20%20%20%20sift_down%28nums%2C%20i%2C%200%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20heap_sort%28nums%29%0A%20%20%20%20print%28%22%E3%83%92%E3%83%BC%E3%83%97%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_sorting/insertion_sort.md b/ja/codes/pythontutor/chapter_sorting/insertion_sort.md new file mode 100644 index 000000000..e853a9274 --- /dev/null +++ b/ja/codes/pythontutor/chapter_sorting/insertion_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20insertion_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E6%8C%BF%E5%85%A5%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E5%A4%96%E5%81%B4%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%95%B4%E5%88%97%E6%B8%88%E3%81%BF%E5%8C%BA%E9%96%93%E3%81%AF%20%5B0%2C%20i-1%5D%0A%20%20%20%20for%20i%20in%20range%281%2C%20len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20base%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20j%20%3D%20i%20-%201%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%81%B4%E3%83%AB%E3%83%BC%E3%83%97%3A%20base%20%E3%82%92%E3%82%BD%E3%83%BC%E3%83%88%E6%B8%88%E3%81%BF%E5%8C%BA%E9%96%93%20%5B0%2C%20i-1%5D%20%E3%81%AE%E6%AD%A3%E3%81%97%E3%81%84%E4%BD%8D%E7%BD%AE%E3%81%AB%E6%8C%BF%E5%85%A5%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20while%20j%20%3E%3D%200%20and%20nums%5Bj%5D%20%3E%20base%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%5D%20%20%23%20nums%5Bj%5D%20%E3%82%92%201%20%E3%81%A4%E5%8F%B3%E3%81%B8%E7%A7%BB%E5%8B%95%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20%20%20%20%20nums%5Bj%20%2B%201%5D%20%3D%20base%20%20%23%20base%20%E3%82%92%E6%AD%A3%E3%81%97%E3%81%84%E4%BD%8D%E7%BD%AE%E3%81%AB%E9%85%8D%E7%BD%AE%E3%81%99%E3%82%8B%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20insertion_sort%28nums%29%0A%20%20%20%20print%28%22%E6%8C%BF%E5%85%A5%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_sorting/merge_sort.md b/ja/codes/pythontutor/chapter_sorting/merge_sort.md new file mode 100644 index 000000000..486af53f8 --- /dev/null +++ b/ja/codes/pythontutor/chapter_sorting/merge_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20merge%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20mid%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E5%B7%A6%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%A8%E5%8F%B3%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%82%92%E3%83%9E%E3%83%BC%E3%82%B8%22%22%22%0A%20%20%20%20%23%20%E5%B7%A6%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E5%8C%BA%E9%96%93%E3%81%AF%20%5Bleft%2C%20mid%5D%E3%80%81%E5%8F%B3%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E5%8C%BA%E9%96%93%E3%81%AF%20%5Bmid%2B1%2C%20right%5D%0A%20%20%20%20%23%20%E3%83%9E%E3%83%BC%E3%82%B8%E7%B5%90%E6%9E%9C%E3%82%92%E6%A0%BC%E7%B4%8D%E3%81%99%E3%82%8B%E4%B8%80%E6%99%82%E9%85%8D%E5%88%97%20tmp%20%E3%82%92%E4%BD%9C%E6%88%90%0A%20%20%20%20tmp%20%3D%20%5B0%5D%20%2A%20%28right%20-%20left%20%2B%201%29%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E9%96%8B%E5%A7%8B%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20i%2C%20j%2C%20k%20%3D%20left%2C%20mid%20%2B%201%2C%200%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AB%E3%81%BE%E3%81%A0%E8%A6%81%E7%B4%A0%E3%81%8C%E3%81%82%E3%82%8B%E9%96%93%E3%81%AF%E6%AF%94%E8%BC%83%E3%81%97%E3%80%81%E5%B0%8F%E3%81%95%E3%81%84%E3%81%BB%E3%81%86%E3%82%92%E4%B8%80%E6%99%82%E9%85%8D%E5%88%97%E3%81%AB%E3%82%B3%E3%83%94%E3%83%BC%E3%81%99%E3%82%8B%0A%20%20%20%20while%20i%20%3C%3D%20mid%20and%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20if%20nums%5Bi%5D%20%3C%3D%20nums%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E6%AE%8B%E3%82%8A%E8%A6%81%E7%B4%A0%E3%82%92%E4%B8%80%E6%99%82%E9%85%8D%E5%88%97%E3%81%AB%E3%82%B3%E3%83%94%E3%83%BC%E3%81%99%E3%82%8B%0A%20%20%20%20while%20i%20%3C%3D%20mid%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bi%5D%0A%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20while%20j%20%3C%3D%20right%3A%0A%20%20%20%20%20%20%20%20tmp%5Bk%5D%20%3D%20nums%5Bj%5D%0A%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20k%20%2B%3D%201%0A%20%20%20%20%23%20%E4%B8%80%E6%99%82%E9%85%8D%E5%88%97%20tmp%20%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E5%85%83%E3%81%AE%E9%85%8D%E5%88%97%20nums%20%E3%81%AE%E5%AF%BE%E5%BF%9C%E5%8C%BA%E9%96%93%E3%81%AB%E3%82%B3%E3%83%94%E3%83%BC%E3%81%99%E3%82%8B%0A%20%20%20%20for%20k%20in%20range%280%2C%20len%28tmp%29%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bleft%20%2B%20k%5D%20%3D%20tmp%5Bk%5D%0A%0A%0Adef%20merge_sort%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E3%83%9E%E3%83%BC%E3%82%B8%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E7%B5%82%E4%BA%86%E6%9D%A1%E4%BB%B6%0A%20%20%20%20if%20left%20%3E%3D%20right%3A%0A%20%20%20%20%20%20%20%20return%20%20%23%20%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E9%95%B7%E3%81%95%E3%81%8C%201%20%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%9F%E3%82%89%E5%86%8D%E5%B8%B0%E3%82%92%E7%B5%82%E4%BA%86%0A%20%20%20%20%23%20%E5%88%86%E5%89%B2%E3%83%95%E3%82%A7%E3%83%BC%E3%82%BA%0A%20%20%20%20mid%20%3D%20%28left%20%2B%20right%29%20%2F%2F%202%20%20%23%20%E4%B8%AD%E7%82%B9%E3%82%92%E8%A8%88%E7%AE%97%0A%20%20%20%20merge_sort%28nums%2C%20left%2C%20mid%29%20%20%23%20%E5%B7%A6%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%82%92%E5%86%8D%E5%B8%B0%E5%87%A6%E7%90%86%0A%20%20%20%20merge_sort%28nums%2C%20mid%20%2B%201%2C%20right%29%20%20%23%20%E5%8F%B3%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%82%92%E5%86%8D%E5%B8%B0%E5%87%A6%E7%90%86%0A%20%20%20%20%23%20%E3%83%9E%E3%83%BC%E3%82%B8%E3%83%95%E3%82%A7%E3%83%BC%E3%82%BA%0A%20%20%20%20merge%28nums%2C%20left%2C%20mid%2C%20right%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B7%2C%203%2C%202%2C%206%2C%200%2C%201%2C%205%2C%204%5D%0A%20%20%20%20merge_sort%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E3%83%9E%E3%83%BC%E3%82%B8%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_sorting/quick_sort.md b/ja/codes/pythontutor/chapter_sorting/quick_sort.md new file mode 100644 index 000000000..ffee07f3e --- /dev/null +++ b/ja/codes/pythontutor/chapter_sorting/quick_sort.md @@ -0,0 +1,17 @@ + + + +https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%95%AA%E5%85%B5%E5%88%86%E5%89%B2%22%22%22%0A%20%20%20%20%23%20nums%5Bleft%5D%20%E3%82%92%E5%9F%BA%E6%BA%96%E5%80%A4%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E5%8F%B3%E3%81%8B%E3%82%89%E5%B7%A6%E3%81%B8%E5%9F%BA%E6%BA%96%E5%80%A4%E6%9C%AA%E6%BA%80%E3%81%AE%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E5%B7%A6%E3%81%8B%E3%82%89%E5%8F%B3%E3%81%B8%E5%9F%BA%E6%BA%96%E5%80%A4%E3%82%88%E3%82%8A%E5%A4%A7%E3%81%8D%E3%81%84%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%81%AE%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%9F%BA%E6%BA%96%E5%80%A4%E3%82%92%202%20%E3%81%A4%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E5%A2%83%E7%95%8C%E3%81%B8%E4%BA%A4%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E5%9F%BA%E6%BA%96%E5%80%A4%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E8%BF%94%E3%81%99%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20partition%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E7%95%AA%E5%85%B5%E5%88%86%E5%89%B2%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%95%AA%E5%85%B5%E5%88%86%E5%89%B2%22%22%22%0A%20%20%20%20%23%20nums%5Bleft%5D%20%E3%82%92%E5%9F%BA%E6%BA%96%E5%80%A4%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E5%8F%B3%E3%81%8B%E3%82%89%E5%B7%A6%E3%81%B8%E5%9F%BA%E6%BA%96%E5%80%A4%E6%9C%AA%E6%BA%80%E3%81%AE%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E5%B7%A6%E3%81%8B%E3%82%89%E5%8F%B3%E3%81%B8%E5%9F%BA%E6%BA%96%E5%80%A4%E3%82%88%E3%82%8A%E5%A4%A7%E3%81%8D%E3%81%84%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%81%AE%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%9F%BA%E6%BA%96%E5%80%A4%E3%82%92%202%20%E3%81%A4%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E5%A2%83%E7%95%8C%E3%81%B8%E4%BA%A4%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E5%9F%BA%E6%BA%96%E5%80%A4%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E8%BF%94%E3%81%99%0A%0Adef%20quick_sort%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E3%82%AF%E3%82%A4%E3%83%83%E3%82%AF%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E9%95%B7%E3%81%95%E3%81%8C%201%20%E3%81%AA%E3%82%89%E5%86%8D%E5%B8%B0%E3%82%92%E7%B5%82%E4%BA%86%E3%81%99%E3%82%8B%0A%20%20%20%20if%20left%20%3E%3D%20right%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E7%95%AA%E5%85%B5%E5%88%86%E5%89%B2%0A%20%20%20%20pivot%20%3D%20partition%28nums%2C%20left%2C%20right%29%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%82%92%E5%86%8D%E5%B8%B0%E5%87%A6%E7%90%86%0A%20%20%20%20quick_sort%28nums%2C%20left%2C%20pivot%20-%201%29%0A%20%20%20%20quick_sort%28nums%2C%20pivot%20%2B%201%2C%20right%29%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E3%82%AF%E3%82%A4%E3%83%83%E3%82%AF%E3%82%BD%E3%83%BC%E3%83%88%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20quick_sort%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E3%82%AF%E3%82%A4%E3%83%83%E3%82%AF%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20median_three%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20mid%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%223%E3%81%A4%E3%81%AE%E5%80%99%E8%A3%9C%E8%A6%81%E7%B4%A0%E3%81%AE%E4%B8%AD%E5%A4%AE%E5%80%A4%E3%82%92%E9%81%B8%E3%81%B6%22%22%22%0A%20%20%20%20l%2C%20m%2C%20r%20%3D%20nums%5Bleft%5D%2C%20nums%5Bmid%5D%2C%20nums%5Bright%5D%0A%20%20%20%20if%20%28l%20%3C%3D%20m%20%3C%3D%20r%29%20or%20%28r%20%3C%3D%20m%20%3C%3D%20l%29%3A%0A%20%20%20%20%20%20%20%20return%20mid%20%20%23%20m%20%E3%81%AF%20l%20%E3%81%A8%20r%20%E3%81%AE%E9%96%93%0A%20%20%20%20if%20%28m%20%3C%3D%20l%20%3C%3D%20r%29%20or%20%28r%20%3C%3D%20l%20%3C%3D%20m%29%3A%0A%20%20%20%20%20%20%20%20return%20left%20%20%23%20l%20%E3%81%AF%20m%20%E3%81%A8%20r%20%E3%81%AE%E9%96%93%0A%20%20%20%20return%20right%0A%0Adef%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%95%AA%E5%85%B5%E3%81%AB%E3%82%88%E3%82%8B%E5%88%86%E5%89%B2%E5%87%A6%E7%90%86%EF%BC%883%20%E7%82%B9%E4%B8%AD%E5%A4%AE%E5%80%A4%EF%BC%89%22%22%22%0A%20%20%20%20%23%20nums%5Bleft%5D%20%E3%82%92%E5%9F%BA%E6%BA%96%E5%80%A4%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20med%20%3D%20median_three%28nums%2C%20left%2C%20%28left%20%2B%20right%29%20%2F%2F%202%2C%20right%29%0A%20%20%20%20%23%20%E4%B8%AD%E5%A4%AE%E5%80%A4%E3%82%92%E9%85%8D%E5%88%97%E3%81%AE%E6%9C%80%E5%B7%A6%E7%AB%AF%E3%81%AB%E4%BA%A4%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20nums%5Bleft%5D%2C%20nums%5Bmed%5D%20%3D%20nums%5Bmed%5D%2C%20nums%5Bleft%5D%0A%20%20%20%20%23%20nums%5Bleft%5D%20%E3%82%92%E5%9F%BA%E6%BA%96%E5%80%A4%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E5%8F%B3%E3%81%8B%E3%82%89%E5%B7%A6%E3%81%B8%E5%9F%BA%E6%BA%96%E5%80%A4%E6%9C%AA%E6%BA%80%E3%81%AE%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E5%B7%A6%E3%81%8B%E3%82%89%E5%8F%B3%E3%81%B8%E5%9F%BA%E6%BA%96%E5%80%A4%E3%82%88%E3%82%8A%E5%A4%A7%E3%81%8D%E3%81%84%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%81%AE%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%9F%BA%E6%BA%96%E5%80%A4%E3%82%92%202%20%E3%81%A4%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E5%A2%83%E7%95%8C%E3%81%B8%E4%BA%A4%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E5%9F%BA%E6%BA%96%E5%80%A4%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E8%BF%94%E3%81%99%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%B8%AD%E5%A4%AE%E5%80%A4%E3%83%94%E3%83%9C%E3%83%83%E3%83%88%E6%9C%80%E9%81%A9%E5%8C%96%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20partition%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E7%95%AA%E5%85%B5%E5%88%86%E5%89%B2%EF%BC%88%E4%B8%AD%E5%A4%AE%E5%80%A4%E3%83%94%E3%83%9C%E3%83%83%E3%83%88%E6%9C%80%E9%81%A9%E5%8C%96%EF%BC%89%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=def%20partition%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E7%95%AA%E5%85%B5%E5%88%86%E5%89%B2%22%22%22%0A%20%20%20%20%23%20nums%5Bleft%5D%20%E3%82%92%E5%9F%BA%E6%BA%96%E5%80%A4%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20i%2C%20j%20%3D%20left%2C%20right%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bj%5D%20%3E%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%20%20%23%20%E5%8F%B3%E3%81%8B%E3%82%89%E5%B7%A6%E3%81%B8%E5%9F%BA%E6%BA%96%E5%80%A4%E6%9C%AA%E6%BA%80%E3%81%AE%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20%20%20%20%20while%20i%20%3C%20j%20and%20nums%5Bi%5D%20%3C%3D%20nums%5Bleft%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%20%20%23%20%E5%B7%A6%E3%81%8B%E3%82%89%E5%8F%B3%E3%81%B8%E5%9F%BA%E6%BA%96%E5%80%A4%E3%82%88%E3%82%8A%E5%A4%A7%E3%81%8D%E3%81%84%E6%9C%80%E5%88%9D%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E6%8E%A2%E3%81%99%0A%20%20%20%20%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%81%AE%E4%BA%A4%E6%8F%9B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bj%5D%20%3D%20nums%5Bj%5D%2C%20nums%5Bi%5D%0A%20%20%20%20%23%20%E5%9F%BA%E6%BA%96%E5%80%A4%E3%82%92%202%20%E3%81%A4%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E5%A2%83%E7%95%8C%E3%81%B8%E4%BA%A4%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20nums%5Bi%5D%2C%20nums%5Bleft%5D%20%3D%20nums%5Bleft%5D%2C%20nums%5Bi%5D%0A%20%20%20%20return%20i%20%20%23%20%E5%9F%BA%E6%BA%96%E5%80%A4%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E8%BF%94%E3%81%99%0A%0Adef%20quick_sort%28nums%3A%20list%5Bint%5D%2C%20left%3A%20int%2C%20right%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E3%82%AF%E3%82%A4%E3%83%83%E3%82%AF%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%88%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%E6%9C%80%E9%81%A9%E5%8C%96%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E9%95%B7%E3%81%95%E3%81%8C%201%20%E3%81%AA%E3%82%89%E7%B5%82%E4%BA%86%0A%20%20%20%20while%20left%20%3C%20right%3A%0A%20%20%20%20%20%20%20%20%23%20%E7%95%AA%E5%85%B5%E3%81%AB%E3%82%88%E3%82%8B%E5%88%86%E5%89%B2%E5%87%A6%E7%90%86%0A%20%20%20%20%20%20%20%20pivot%20%3D%20partition%28nums%2C%20left%2C%20right%29%0A%20%20%20%20%20%20%20%20%23%202%20%E3%81%A4%E3%81%AE%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%81%AE%E3%81%86%E3%81%A1%E7%9F%AD%E3%81%84%E3%81%BB%E3%81%86%E3%81%AB%E3%82%AF%E3%82%A4%E3%83%83%E3%82%AF%E3%82%BD%E3%83%BC%E3%83%88%E3%82%92%E9%81%A9%E7%94%A8%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20if%20pivot%20-%20left%20%3C%20right%20-%20pivot%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20quick_sort%28nums%2C%20left%2C%20pivot%20-%201%29%20%20%23%20%E5%B7%A6%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E3%82%BD%E3%83%BC%E3%83%88%0A%20%20%20%20%20%20%20%20%20%20%20%20left%20%3D%20pivot%20%2B%201%20%20%23%20%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%E3%81%AE%E6%AE%8B%E3%82%8A%E3%81%AF%20%5Bpivot%20%2B%201%2C%20right%5D%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20quick_sort%28nums%2C%20pivot%20%2B%201%2C%20right%29%20%20%23%20%E5%8F%B3%E9%83%A8%E5%88%86%E9%85%8D%E5%88%97%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E3%82%BD%E3%83%BC%E3%83%88%0A%20%20%20%20%20%20%20%20%20%20%20%20right%20%3D%20pivot%20-%201%20%20%23%20%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%E3%81%AE%E6%AE%8B%E3%82%8A%E3%81%AF%20%5Bleft%2C%20pivot%20-%201%5D%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E3%82%AF%E3%82%A4%E3%83%83%E3%82%AF%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%88%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%E6%9C%80%E9%81%A9%E5%8C%96%EF%BC%89%0A%20%20%20%20nums%20%3D%20%5B2%2C%204%2C%201%2C%200%2C%203%2C%205%5D%0A%20%20%20%20quick_sort%28nums%2C%200%2C%20len%28nums%29%20-%201%29%0A%20%20%20%20print%28%22%E3%82%AF%E3%82%A4%E3%83%83%E3%82%AF%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%88%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%E6%9C%80%E9%81%A9%E5%8C%96%EF%BC%89%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_sorting/radix_sort.md b/ja/codes/pythontutor/chapter_sorting/radix_sort.md new file mode 100644 index 000000000..d16d4b413 --- /dev/null +++ b/ja/codes/pythontutor/chapter_sorting/radix_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20digit%28num%3A%20int%2C%20exp%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E8%A6%81%E7%B4%A0%20num%20%E3%81%AE%E4%B8%8B%E3%81%8B%E3%82%89%20k%20%E6%A1%81%E7%9B%AE%E3%82%92%E5%8F%96%E5%BE%97%EF%BC%88exp%20%3D%2010%5E%28k-1%29%EF%BC%89%22%22%22%0A%20%20%20%20%23%20%E3%81%93%E3%81%93%E3%81%A7%E9%AB%98%E3%82%B3%E3%82%B9%E3%83%88%E3%81%AA%E7%B4%AF%E4%B9%97%E8%A8%88%E7%AE%97%E3%82%92%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%95%E3%81%AA%E3%81%84%E3%82%88%E3%81%86%E3%80%81k%20%E3%81%A7%E3%81%AF%E3%81%AA%E3%81%8F%20exp%20%E3%82%92%E6%B8%A1%E3%81%99%0A%20%20%20%20return%20%28num%20%2F%2F%20exp%29%20%25%2010%0A%0Adef%20counting_sort_digit%28nums%3A%20list%5Bint%5D%2C%20exp%3A%20int%29%3A%0A%20%20%20%20%22%22%22%E8%A8%88%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%88nums%20%E3%81%AE%20k%20%E6%A1%81%E7%9B%AE%E3%81%A7%E3%82%BD%E3%83%BC%E3%83%88%EF%BC%89%22%22%22%0A%20%20%20%20%23%2010%20%E9%80%B2%E6%95%B0%E3%81%AE%E5%90%84%E6%A1%81%E3%81%AF%200~9%20%E3%81%AE%E7%AF%84%E5%9B%B2%E3%81%AA%E3%81%AE%E3%81%A7%E3%80%81%E9%95%B7%E3%81%95%2010%20%E3%81%AE%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E9%85%8D%E5%88%97%E3%81%8C%E5%BF%85%E8%A6%81%0A%20%20%20%20counter%20%3D%20%5B0%5D%20%2A%2010%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%200~9%20%E3%81%AE%E5%90%84%E6%95%B0%E5%AD%97%E3%81%AE%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%82%92%E9%9B%86%E8%A8%88%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D%2C%20exp%29%20%20%23%20nums%5Bi%5D%20%E3%81%AE%E7%AC%AC%20k%20%E4%BD%8D%E3%82%92%E5%8F%96%E5%BE%97%E3%81%97%E3%80%81d%20%E3%81%A8%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20%2B%3D%201%20%20%23%20%E6%95%B0%E5%AD%97%20d%20%E3%81%AE%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%82%92%E6%95%B0%E3%81%88%E3%82%8B%0A%20%20%20%20%23%20%E7%B4%AF%E7%A9%8D%E5%92%8C%E3%82%92%E6%B1%82%E3%82%81%E3%80%81%E3%80%8C%E5%87%BA%E7%8F%BE%E5%9B%9E%E6%95%B0%E3%80%8D%E3%82%92%E3%80%8C%E9%85%8D%E5%88%97%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%80%8D%E3%81%AB%E5%A4%89%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%281%2C%2010%29%3A%0A%20%20%20%20%20%20%20%20counter%5Bi%5D%20%2B%3D%20counter%5Bi%20-%201%5D%0A%20%20%20%20%23%20%E9%80%86%E9%A0%86%E3%81%AB%E8%B5%B0%E6%9F%BB%E3%81%97%E3%80%81%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E5%86%85%E3%81%AE%E9%9B%86%E8%A8%88%E7%B5%90%E6%9E%9C%E3%81%AB%E5%BE%93%E3%81%A3%E3%81%A6%E5%90%84%E8%A6%81%E7%B4%A0%E3%82%92%20res%20%E3%81%AB%E6%A0%BC%E7%B4%8D%E3%81%99%E3%82%8B%0A%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20n%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%20-1%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20d%20%3D%20digit%28nums%5Bi%5D%2C%20exp%29%0A%20%20%20%20%20%20%20%20j%20%3D%20counter%5Bd%5D%20-%201%20%20%23%20d%20%E3%81%AE%E9%85%8D%E5%88%97%E5%86%85%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20j%20%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20res%5Bj%5D%20%3D%20nums%5Bi%5D%20%20%23%20%E7%8F%BE%E5%9C%A8%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%20j%20%E3%81%AB%E6%A0%BC%E7%B4%8D%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20counter%5Bd%5D%20-%3D%201%20%20%23%20d%20%E3%81%AE%E5%80%8B%E6%95%B0%E3%82%92%201%20%E6%B8%9B%E3%82%89%E3%81%99%0A%20%20%20%20%23%20%E7%B5%90%E6%9E%9C%E3%81%A7%E5%85%83%E3%81%AE%E9%85%8D%E5%88%97%20nums%20%E3%82%92%E4%B8%8A%E6%9B%B8%E3%81%8D%E3%81%99%E3%82%8B%0A%20%20%20%20for%20i%20in%20range%28n%29%3A%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%20%3D%20res%5Bi%5D%0A%0Adef%20radix_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E5%9F%BA%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20%23%20%E6%9C%80%E5%A4%A7%E6%A1%81%E6%95%B0%E3%81%AE%E5%88%A4%E5%AE%9A%E7%94%A8%E3%81%AB%E9%85%8D%E5%88%97%E3%81%AE%E6%9C%80%E5%A4%A7%E8%A6%81%E7%B4%A0%E3%82%92%E5%8F%96%E5%BE%97%0A%20%20%20%20m%20%3D%20max%28nums%29%0A%20%20%20%20%23%20%E4%B8%8B%E4%BD%8D%E6%A1%81%E3%81%8B%E3%82%89%E4%B8%8A%E4%BD%8D%E6%A1%81%E3%81%AE%E9%A0%86%E3%81%AB%E8%B5%B0%E6%9F%BB%E3%81%99%E3%82%8B%0A%20%20%20%20exp%20%3D%201%0A%20%20%20%20while%20exp%20%3C%3D%20m%3A%0A%20%20%20%20%20%20%20%20%23%20%E9%85%8D%E5%88%97%E8%A6%81%E7%B4%A0%E3%81%AE%20k%20%E6%A1%81%E7%9B%AE%E3%81%AB%E5%AF%BE%E3%81%97%E3%81%A6%E8%A8%88%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%E3%82%92%E8%A1%8C%E3%81%86%0A%20%20%20%20%20%20%20%20%23%20k%20%3D%201%20-%3E%20exp%20%3D%201%0A%20%20%20%20%20%20%20%20%23%20k%20%3D%202%20-%3E%20exp%20%3D%2010%0A%20%20%20%20%20%20%20%20%23%20%E3%81%A4%E3%81%BE%E3%82%8A%20exp%20%3D%2010%5E%28k-1%29%0A%20%20%20%20%20%20%20%20counting_sort_digit%28nums%2C%20exp%29%0A%20%20%20%20%20%20%20%20exp%20%2A%3D%2010%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%9F%BA%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%0A%20%20%20%20nums%20%3D%20%5B%0A%20%20%20%20%20%20%20%20105%2C%0A%20%20%20%20%20%20%20%20356%2C%0A%20%20%20%20%20%20%20%20428%2C%0A%20%20%20%20%20%20%20%20348%2C%0A%20%20%20%20%20%20%20%20818%2C%0A%20%20%20%20%5D%0A%20%20%20%20radix_sort%28nums%29%0A%20%20%20%20print%28%22%E5%9F%BA%E6%95%B0%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_sorting/selection_sort.md b/ja/codes/pythontutor/chapter_sorting/selection_sort.md new file mode 100644 index 000000000..49567c48a --- /dev/null +++ b/ja/codes/pythontutor/chapter_sorting/selection_sort.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=def%20selection_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%E9%81%B8%E6%8A%9E%E3%82%BD%E3%83%BC%E3%83%88%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%E5%A4%96%E5%81%B4%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E6%95%B4%E5%88%97%E5%8C%BA%E9%96%93%E3%81%AF%20%5Bi%2C%20n-1%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%29%3A%0A%20%20%20%20%20%20%20%20%23%20%E5%86%85%E5%81%B4%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%97%EF%BC%9A%E6%9C%AA%E3%82%BD%E3%83%BC%E3%83%88%E5%8C%BA%E9%96%93%E3%81%AE%E6%9C%80%E5%B0%8F%E8%A6%81%E7%B4%A0%E3%82%92%E8%A6%8B%E3%81%A4%E3%81%91%E3%82%8B%0A%20%20%20%20%20%20%20%20k%20%3D%20i%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%20%2B%201%2C%20n%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3C%20nums%5Bk%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20k%20%3D%20j%20%20%23%20%E6%9C%80%E5%B0%8F%E8%A6%81%E7%B4%A0%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E8%A8%98%E9%8C%B2%0A%20%20%20%20%20%20%20%20%23%20%E3%81%9D%E3%81%AE%E6%9C%80%E5%B0%8F%E8%A6%81%E7%B4%A0%E3%82%92%E6%9C%AA%E6%95%B4%E5%88%97%E5%8C%BA%E9%96%93%E3%81%AE%E5%85%88%E9%A0%AD%E8%A6%81%E7%B4%A0%E3%81%A8%E4%BA%A4%E6%8F%9B%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20nums%5Bi%5D%2C%20nums%5Bk%5D%20%3D%20nums%5Bk%5D%2C%20nums%5Bi%5D%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20selection_sort%28nums%29%0A%20%20%20%20print%28%22%E9%81%B8%E6%8A%9E%E3%82%BD%E3%83%BC%E3%83%88%E5%AE%8C%E4%BA%86%E5%BE%8C%20nums%20%3D%22%2C%20nums%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_stack_and_queue/array_queue.md b/ja/codes/pythontutor/chapter_stack_and_queue/array_queue.md new file mode 100644 index 000000000..693eaa7c4 --- /dev/null +++ b/ja/codes/pythontutor/chapter_stack_and_queue/array_queue.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20ArrayQueue%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20size%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self._nums%3A%20list%5Bint%5D%20%3D%20%5B0%5D%20%2A%20size%0A%20%20%20%20%20%20%20%20self._front%3A%20int%20%3D%200%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20capacity%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20len%28self._nums%29%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20return%20self._size%20%3D%3D%200%0A%0A%20%20%20%20def%20push%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20if%20self._size%20%3D%3D%20self.capacity%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%8C%E3%81%84%E3%81%A3%E3%81%B1%E3%81%84%E3%81%A7%E3%81%99%27%29%0A%20%20%20%20%20%20%20%20rear%3A%20int%20%3D%20%28self._front%20%2B%20self._size%29%20%25%20self.capacity%28%29%0A%20%20%20%20%20%20%20%20self._nums%5Brear%5D%20%3D%20num%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20num%3A%20int%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20self._front%20%3D%20%28self._front%20%2B%201%29%20%25%20self.capacity%28%29%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%8C%E7%A9%BA%E3%81%A7%E3%81%99%27%29%0A%20%20%20%20%20%20%20%20return%20self._nums%5Bself._front%5D%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20res%20%3D%20%5B0%5D%20%2A%20self.size%28%29%0A%20%20%20%20%20%20%20%20j%3A%20int%20%3D%20self._front%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.size%28%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20res%5Bi%5D%20%3D%20self._nums%5Bj%20%25%20self.capacity%28%29%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20%2B%3D%201%0A%20%20%20%20%20%20%20%20return%20res%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20queue%20%3D%20ArrayQueue%2810%29%0A%20%20%20%20queue.push%281%29%0A%20%20%20%20queue.push%283%29%0A%20%20%20%20queue.push%282%29%0A%20%20%20%20queue.push%285%29%0A%20%20%20%20queue.push%284%29%0A%20%20%20%20peek%20%3D%20queue.peek%28%29%0A%20%20%20%20print%28%27%E5%85%88%E9%A0%AD%E8%A6%81%E7%B4%A0%20peek%20%3D%27%2C%20peek%29%0A%20%20%20%20pop%20%3D%20queue.pop%28%29%0A%20%20%20%20print%28%27%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%97%E3%81%9F%E8%A6%81%E7%B4%A0%20pop%20%3D%27%2C%20pop%29%0A%20%20%20%20size%20%3D%20queue.size%28%29%0A%20%20%20%20print%28%27%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%AE%E9%95%B7%E3%81%95%20size%20%3D%27%2C%20size%29%0A%20%20%20%20is_empty%20%3D%20queue.is_empty%28%29%0A%20%20%20%20print%28%27%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%8C%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%20%3D%27%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_stack_and_queue/array_stack.md b/ja/codes/pythontutor/chapter_stack_and_queue/array_stack.md new file mode 100644 index 000000000..ec6035663 --- /dev/null +++ b/ja/codes/pythontutor/chapter_stack_and_queue/array_stack.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20ArrayStack%3A%0A%20%20%20%20%22%22%22%E9%85%8D%E5%88%97%E3%83%99%E3%83%BC%E3%82%B9%E3%81%AE%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%22%22%22%0A%20%20%20%20%20%20%20%20self._stack%3A%20list%5Bint%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%AE%E9%95%B7%E3%81%95%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%20len%28self._stack%29%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%8C%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%E3%82%92%E5%88%A4%E5%AE%9A%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._stack%20%3D%3D%20%5B%5D%0A%0A%20%20%20%20def%20push%28self%2C%20item%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%22%22%22%0A%20%20%20%20%20%20%20%20self._stack.append%28item%29%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%9D%E3%83%83%E3%83%97%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%8C%E7%A9%BA%E3%81%A7%E3%81%99%22%29%0A%20%20%20%20%20%20%20%20return%20self._stack.pop%28%29%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%83%88%E3%83%83%E3%83%97%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%8C%E7%A9%BA%E3%81%A7%E3%81%99%22%29%0A%20%20%20%20%20%20%20%20return%20self._stack%5B-1%5D%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A1%A8%E7%A4%BA%E7%94%A8%E3%81%AE%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E8%BF%94%E3%81%99%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._stack%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20stack%20%3D%20ArrayStack%28%29%0A%0A%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%82%92%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%0A%20%20%20%20stack.push%281%29%0A%20%20%20%20stack.push%283%29%0A%20%20%20%20stack.push%282%29%0A%20%20%20%20stack.push%285%29%0A%20%20%20%20stack.push%284%29%0A%20%20%20%20print%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%20stack%20%3D%22%2C%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%83%88%E3%83%83%E3%83%97%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%0A%20%20%20%20peek%20%3D%20stack.peek%28%29%0A%20%20%20%20print%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%83%88%E3%83%83%E3%83%97%E8%A6%81%E7%B4%A0%20peek%20%3D%22%2C%20peek%29%0A%0A%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%82%92%E3%83%9D%E3%83%83%E3%83%97%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E3%83%9D%E3%83%83%E3%83%97%E3%81%97%E3%81%9F%E8%A6%81%E7%B4%A0%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22%E3%83%9D%E3%83%83%E3%83%97%E5%BE%8C%20stack%20%3D%22%2C%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%AE%E9%95%B7%E3%81%95%E3%82%92%E5%8F%96%E5%BE%97%0A%20%20%20%20size%20%3D%20stack.size%28%29%0A%20%20%20%20print%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%AE%E9%95%B7%E3%81%95%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%E3%82%92%E5%88%A4%E5%AE%9A%0A%20%20%20%20is_empty%20%3D%20stack.is_empty%28%29%0A%20%20%20%20print%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%8C%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_stack_and_queue/linkedlist_queue.md b/ja/codes/pythontutor/chapter_stack_and_queue/linkedlist_queue.md new file mode 100644 index 000000000..bc773d726 --- /dev/null +++ b/ja/codes/pythontutor/chapter_stack_and_queue/linkedlist_queue.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%0A%0Aclass%20LinkedListQueue%3A%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20self._front%3A%20ListNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self._rear%3A%20ListNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20return%20not%20self._front%0A%0A%20%20%20%20def%20push%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20node%20%3D%20ListNode%28num%29%0A%20%20%20%20%20%20%20%20if%20self._front%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._front%20%3D%20node%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear.next%20%3D%20node%0A%20%20%20%20%20%20%20%20%20%20%20%20self._rear%20%3D%20node%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20num%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20self._front%20%3D%20self._front.next%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%27%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%8C%E7%A9%BA%E3%81%A7%E3%81%99%27%29%0A%20%20%20%20%20%20%20%20return%20self._front.val%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20queue%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20temp%20%3D%20self._front%0A%20%20%20%20%20%20%20%20while%20temp%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28temp.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20temp%20%3D%20temp.next%0A%20%20%20%20%20%20%20%20return%20queue%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20queue%20%3D%20LinkedListQueue%28%29%0A%20%20%20%20queue.push%281%29%0A%20%20%20%20queue.push%283%29%0A%20%20%20%20queue.push%282%29%0A%20%20%20%20queue.push%285%29%0A%20%20%20%20queue.push%284%29%0A%20%20%20%20print%28%27%E3%82%AD%E3%83%A5%E3%83%BC%20queue%20%3D%27%2C%20queue.to_list%28%29%29%0A%20%20%20%20peek%20%3D%20queue.peek%28%29%0A%20%20%20%20print%28%27%E5%85%88%E9%A0%AD%E8%A6%81%E7%B4%A0%20front%20%3D%27%2C%20peek%29%0A%20%20%20%20pop_front%20%3D%20queue.pop%28%29%0A%20%20%20%20print%28%27%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%97%E3%81%9F%E8%A6%81%E7%B4%A0%20pop%20%3D%27%2C%20pop_front%29%0A%20%20%20%20print%28%27%E5%8F%96%E3%82%8A%E5%87%BA%E3%81%97%E3%81%9F%E5%BE%8C%20queue%20%3D%27%2C%20queue.to_list%28%29%29%0A%20%20%20%20size%20%3D%20queue.size%28%29%0A%20%20%20%20print%28%27%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%AE%E9%95%B7%E3%81%95%20size%20%3D%27%2C%20size%29%0A%20%20%20%20is_empty%20%3D%20queue.is_empty%28%29%0A%20%20%20%20print%28%27%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%8C%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%20%3D%27%2C%20is_empty%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_stack_and_queue/linkedlist_stack.md b/ja/codes/pythontutor/chapter_stack_and_queue/linkedlist_stack.md new file mode 100644 index 000000000..45381a4a1 --- /dev/null +++ b/ja/codes/pythontutor/chapter_stack_and_queue/linkedlist_stack.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%BE%8C%E7%B6%9A%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0A%0Aclass%20LinkedListStack%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%83%99%E3%83%BC%E3%82%B9%E3%81%AE%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%22%22%22%0A%20%20%20%20%20%20%20%20self._peek%3A%20ListNode%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20self._size%3A%20int%20%3D%200%0A%0A%20%20%20%20def%20size%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%AE%E9%95%B7%E3%81%95%E3%82%92%E5%8F%96%E5%BE%97%22%22%22%0A%20%20%20%20%20%20%20%20return%20self._size%0A%0A%20%20%20%20def%20is_empty%28self%29%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%8C%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%E3%82%92%E5%88%A4%E5%AE%9A%22%22%22%0A%20%20%20%20%20%20%20%20return%20not%20self._peek%0A%0A%20%20%20%20def%20push%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%22%22%22%0A%20%20%20%20%20%20%20%20node%20%3D%20ListNode%28val%29%0A%20%20%20%20%20%20%20%20node.next%20%3D%20self._peek%0A%20%20%20%20%20%20%20%20self._peek%20%3D%20node%0A%20%20%20%20%20%20%20%20self._size%20%2B%3D%201%0A%0A%20%20%20%20def%20pop%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%9D%E3%83%83%E3%83%97%22%22%22%0A%20%20%20%20%20%20%20%20num%20%3D%20self.peek%28%29%0A%20%20%20%20%20%20%20%20self._peek%20%3D%20self._peek.next%0A%20%20%20%20%20%20%20%20self._size%20-%3D%201%0A%20%20%20%20%20%20%20%20return%20num%0A%0A%20%20%20%20def%20peek%28self%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%83%88%E3%83%83%E3%83%97%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%22%22%22%0A%20%20%20%20%20%20%20%20if%20self.is_empty%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20IndexError%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%8C%E7%A9%BA%E3%81%A7%E3%81%99%22%29%0A%20%20%20%20%20%20%20%20return%20self._peek.val%0A%0A%20%20%20%20def%20to_list%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E8%A1%A8%E7%A4%BA%E7%94%A8%E3%81%AB%E3%83%AA%E3%82%B9%E3%83%88%E3%81%B8%E5%A4%89%E6%8F%9B%22%22%22%0A%20%20%20%20%20%20%20%20arr%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20node%20%3D%20self._peek%0A%20%20%20%20%20%20%20%20while%20node%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20arr.append%28node.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20node%20%3D%20node.next%0A%20%20%20%20%20%20%20%20arr.reverse%28%29%0A%20%20%20%20%20%20%20%20return%20arr%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20stack%20%3D%20LinkedListStack%28%29%0A%0A%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%82%92%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%0A%20%20%20%20stack.push%281%29%0A%20%20%20%20stack.push%283%29%0A%20%20%20%20stack.push%282%29%0A%20%20%20%20stack.push%285%29%0A%20%20%20%20stack.push%284%29%0A%20%20%20%20print%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%20stack%20%3D%22%2C%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%83%88%E3%83%83%E3%83%97%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%0A%20%20%20%20peek%20%3D%20stack.peek%28%29%0A%20%20%20%20print%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%83%88%E3%83%83%E3%83%97%E8%A6%81%E7%B4%A0%20peek%20%3D%22%2C%20peek%29%0A%0A%20%20%20%20%23%20%E8%A6%81%E7%B4%A0%E3%82%92%E3%83%9D%E3%83%83%E3%83%97%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E3%83%9D%E3%83%83%E3%83%97%E3%81%97%E3%81%9F%E8%A6%81%E7%B4%A0%20pop%20%3D%22%2C%20pop%29%0A%20%20%20%20print%28%22%E3%83%9D%E3%83%83%E3%83%97%E5%BE%8C%20stack%20%3D%22%2C%20stack.to_list%28%29%29%0A%0A%20%20%20%20%23%20%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%AE%E9%95%B7%E3%81%95%E3%82%92%E5%8F%96%E5%BE%97%0A%20%20%20%20size%20%3D%20stack.size%28%29%0A%20%20%20%20print%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%AE%E9%95%B7%E3%81%95%20size%20%3D%22%2C%20size%29%0A%0A%20%20%20%20%23%20%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%E3%82%92%E5%88%A4%E5%AE%9A%0A%20%20%20%20is_empty%20%3D%20stack.is_empty%28%29%0A%20%20%20%20print%28%22%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%8C%E7%A9%BA%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%20%3D%22%2C%20is_empty%29&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_tree/array_binary_tree.md b/ja/codes/pythontutor/chapter_tree/array_binary_tree.md new file mode 100644 index 000000000..8db14b416 --- /dev/null +++ b/ja/codes/pythontutor/chapter_tree/array_binary_tree.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20ArrayBinaryTree%3A%0A%0A%20%20%20%20def%20__init__%28self%2C%20arr%3A%20list%5Bint%20%7C%20None%5D%29%3A%0A%20%20%20%20%20%20%20%20self._tree%20%3D%20list%28arr%29%0A%0A%20%20%20%20def%20size%28self%29%3A%0A%20%20%20%20%20%20%20%20return%20len%28self._tree%29%0A%0A%20%20%20%20def%20val%28self%2C%20i%3A%20int%29%20-%3E%20int%3A%0A%20%20%20%20%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20self.size%28%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%20%20%20%20return%20self._tree%5Bi%5D%0A%0A%20%20%20%20def%20left%28self%2C%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%201%0A%0A%20%20%20%20def%20right%28self%2C%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20return%202%20%2A%20i%20%2B%202%0A%0A%20%20%20%20def%20parent%28self%2C%20i%3A%20int%29%20-%3E%20int%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20return%20%28i%20-%201%29%20%2F%2F%202%0A%0A%20%20%20%20def%20level_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20i%20in%20range%28self.size%28%29%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.val%28i%29%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20dfs%28self%2C%20i%3A%20int%2C%20order%3A%20str%29%3A%0A%20%20%20%20%20%20%20%20if%20self.val%28i%29%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%27pre%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20self.dfs%28self.left%28i%29%2C%20order%29%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%27in%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%20%20%20%20%20%20%20%20self.dfs%28self.right%28i%29%2C%20order%29%0A%20%20%20%20%20%20%20%20if%20order%20%3D%3D%20%27post%27%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self.res.append%28self.val%28i%29%29%0A%0A%20%20%20%20def%20pre_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280%2C%20order%3D%27pre%27%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20in_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280%2C%20order%3D%27in%27%29%0A%20%20%20%20%20%20%20%20return%20self.res%0A%0A%20%20%20%20def%20post_order%28self%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%20%20%20%20self.res%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20self.dfs%280%2C%20order%3D%27post%27%29%0A%20%20%20%20%20%20%20%20return%20self.res%0Aif%20__name__%20%3D%3D%20%27__main__%27%3A%0A%20%20%20%20arr%20%3D%20%5B1%2C%202%2C%203%2C%204%2C%20None%2C%206%2C%20None%5D%0A%20%20%20%20abt%20%3D%20ArrayBinaryTree%28arr%29%0A%20%20%20%20i%20%3D%201%0A%20%20%20%20l%2C%20r%2C%20p%20%3D%20%28abt.left%28i%29%2C%20abt.right%28i%29%2C%20abt.parent%28i%29%29%0A%20%20%20%20res%20%3D%20abt.level_order%28%29%0A%20%20%20%20res%20%3D%20abt.pre_order%28%29%0A%20%20%20%20res%20%3D%20abt.in_order%28%29%0A%20%20%20%20res%20%3D%20abt.post_order%28%29&cumulative=false&curInstr=5&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_tree/binary_search_tree.md b/ja/codes/pythontutor/chapter_tree/binary_search_tree.md new file mode 100644 index 000000000..41a60d650 --- /dev/null +++ b/ja/codes/pythontutor/chapter_tree/binary_search_tree.md @@ -0,0 +1,14 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E6%9C%A8%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E7%A9%BA%E3%81%AE%E6%9C%A8%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20search%28self%2C%20num%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8E%A2%E7%B4%A2%22%22%22%0A%20%20%20%20%20%20%20%20cur%20%3D%20self._root%0A%20%20%20%20%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%A7%E6%8E%A2%E7%B4%A2%E3%81%97%E3%80%81%E8%91%89%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E8%B6%8A%E3%81%88%E3%81%9F%E3%82%89%E6%8A%9C%E3%81%91%E3%82%8B%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A8%99%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AF%20cur%20%E3%81%AE%E5%8F%B3%E9%83%A8%E5%88%86%E6%9C%A8%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A8%99%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AF%20cur%20%E3%81%AE%E5%B7%A6%E9%83%A8%E5%88%86%E6%9C%A8%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20cur.val%20%3E%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%9B%AE%E6%A8%99%E3%83%8E%E3%83%BC%E3%83%89%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E3%82%89%E3%83%AB%E3%83%BC%E3%83%97%E3%82%92%E6%8A%9C%E3%81%91%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20return%20cur%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8C%BF%E5%85%A5%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E6%9C%A8%E3%81%8C%E7%A9%BA%E3%81%AA%E3%82%89%E3%80%81%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%A7%E6%8E%A2%E7%B4%A2%E3%81%97%E3%80%81%E8%91%89%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E8%B6%8A%E3%81%88%E3%81%9F%E3%82%89%E6%8A%9C%E3%81%91%E3%82%8B%0A%20%20%20%20%20%20%20%20cur%2C%20pre%20%3D%20self._root%2C%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E9%87%8D%E8%A4%87%E3%83%8E%E3%83%BC%E3%83%89%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E3%82%89%E3%80%81%E7%9B%B4%E3%81%A1%E3%81%AB%E8%BF%94%E3%81%99%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%81%AF%20cur%20%E3%81%AE%E5%8F%B3%E9%83%A8%E5%88%86%E6%9C%A8%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%81%AF%20cur%20%E3%81%AE%E5%B7%A6%E9%83%A8%E5%88%86%E6%9C%A8%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8C%BF%E5%85%A5%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E6%9C%A8%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4%2C%202%2C%206%2C%201%2C%203%2C%205%2C%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8E%A2%E7%B4%A2%0A%20%20%20%20node%20%3D%20bst.search%287%29%0A%20%20%20%20print%28%22%5Cn%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AF%3A%20%7B%7D%E3%80%81%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E5%80%A4%20%3D%20%7B%7D%22.format%28node%2C%20node.val%29%29&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E6%9C%A8%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E7%A9%BA%E3%81%AE%E6%9C%A8%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8C%BF%E5%85%A5%22%22%22%0A%20%20%20%20%20%20%20%20%23%20%E6%9C%A8%E3%81%8C%E7%A9%BA%E3%81%AA%E3%82%89%E3%80%81%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E3%83%AB%E3%83%BC%E3%83%97%E3%81%A7%E6%8E%A2%E7%B4%A2%E3%81%97%E3%80%81%E8%91%89%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E8%B6%8A%E3%81%88%E3%81%9F%E3%82%89%E6%8A%9C%E3%81%91%E3%82%8B%0A%20%20%20%20%20%20%20%20cur%2C%20pre%20%3D%20self._root%2C%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E9%87%8D%E8%A4%87%E3%83%8E%E3%83%BC%E3%83%89%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%9F%E3%82%89%E3%80%81%E7%9B%B4%E3%81%A1%E3%81%AB%E8%BF%94%E3%81%99%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%81%AF%20cur%20%E3%81%AE%E5%8F%B3%E9%83%A8%E5%88%86%E6%9C%A8%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E6%8C%BF%E5%85%A5%E4%BD%8D%E7%BD%AE%E3%81%AF%20cur%20%E3%81%AE%E5%B7%A6%E9%83%A8%E5%88%86%E6%9C%A8%E3%81%AB%E3%81%82%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8C%BF%E5%85%A5%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E6%9C%A8%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4%2C%202%2C%206%2C%201%2C%203%2C%205%2C%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8C%BF%E5%85%A5%0A%20%20%20%20bst.insert%2816%29&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%29%3A%0A%20%20%20%20%20%20%20%20self.val%20%3D%20val%0A%20%20%20%20%20%20%20%20self.left%20%3D%20None%0A%20%20%20%20%20%20%20%20self.right%20%3D%20None%0A%0Aclass%20BinarySearchTree%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E6%9C%A8%22%22%22%0A%0A%20%20%20%20def%20__init__%28self%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%22%22%22%0A%20%20%20%20%20%20%20%20self._root%20%3D%20None%0A%0A%20%20%20%20def%20insert%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8C%BF%E5%85%A5%22%22%22%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20cur%2C%20pre%20%3D%20self._root%2C%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20node%20%3D%20TreeNode%28num%29%0A%20%20%20%20%20%20%20%20if%20pre.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20node%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20node%0A%0A%20%20%20%20def%20remove%28self%2C%20num%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%89%8A%E9%99%A4%22%22%22%0A%20%20%20%20%20%20%20%20if%20self._root%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%8E%A2%E7%B4%A2%0A%20%20%20%20%20%20%20%20cur%2C%20pre%20%3D%20self._root%2C%20None%0A%20%20%20%20%20%20%20%20while%20cur%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3D%3D%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20pre%20%3D%20cur%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur.val%20%3C%20num%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cur%20%3D%20cur.left%0A%20%20%20%20%20%20%20%20if%20cur%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%0A%20%20%20%20%20%20%20%20%23%20%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E6%95%B0%20%3D%200%20or%201%0A%20%20%20%20%20%20%20%20if%20cur.left%20is%20None%20or%20cur.right%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E6%95%B0%E3%81%8C%200%20%2F%201%20%E3%81%AE%E3%81%A8%E3%81%8D%E3%80%81child%20%3D%20null%20%2F%20%E3%81%9D%E3%81%AE%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%0A%20%20%20%20%20%20%20%20%20%20%20%20child%20%3D%20cur.left%20or%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%20cur%20%E3%82%92%E5%89%8A%E9%99%A4%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20cur%20%21%3D%20self._root%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20pre.left%20%3D%3D%20cur%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pre.left%20%3D%20child%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pre.right%20%3D%20child%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self._root%20%3D%20child%0A%20%20%20%20%20%20%20%20%23%20%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E6%95%B0%20%3D%202%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%B8%AD%E9%A0%86%E8%B5%B0%E6%9F%BB%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%20cur%20%E3%81%AE%E6%AC%A1%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%8F%96%E5%BE%97%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%3A%20TreeNode%20%3D%20cur.right%0A%20%20%20%20%20%20%20%20%20%20%20%20while%20tmp.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20tmp.left%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%20tmp%20%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E5%89%8A%E9%99%A4%0A%20%20%20%20%20%20%20%20%20%20%20%20self.remove%28tmp.val%29%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20tmp%20%E3%81%A7%20cur%20%E3%82%92%E4%B8%8A%E6%9B%B8%E3%81%8D%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20%20%20%20%20cur.val%20%3D%20tmp.val%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%8E%A2%E7%B4%A2%E6%9C%A8%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20bst%20%3D%20BinarySearchTree%28%29%0A%20%20%20%20nums%20%3D%20%5B4%2C%202%2C%206%2C%201%2C%203%2C%205%2C%207%5D%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20bst.insert%28num%29%0A%0A%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E5%89%8A%E9%99%A4%0A%20%20%20%20bst.remove%281%29%20%23%20%E6%AC%A1%E6%95%B0%200%0A%20%20%20%20bst.remove%282%29%20%23%20%E6%AC%A1%E6%95%B0%201%0A%20%20%20%20bst.remove%284%29%20%23%20%E6%AC%A1%E6%95%B0%202&cumulative=false&curInstr=162&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_tree/binary_tree_bfs.md b/ja/codes/pythontutor/chapter_tree/binary_tree_bfs.md new file mode 100644 index 000000000..ea16572fd --- /dev/null +++ b/ja/codes/pythontutor/chapter_tree/binary_tree_bfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0Aclass%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AB%E3%83%87%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8B%3A%20%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E6%B7%BB%E5%AD%97%E3%81%8C%E9%85%8D%E5%88%97%E9%95%B7%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%8B%E3%80%81%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B%E8%A6%81%E7%B4%A0%E3%81%8C%20None%20%E3%81%AA%E3%82%89%E3%80%81None%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E7%8F%BE%E5%9C%A8%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E6%9C%A8%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AB%E3%83%87%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20level_order%28root%3A%20TreeNode%20%7C%20None%29%20-%3E%20list%5Bint%5D%3A%0A%20%20%20%20%22%22%22%E3%83%AC%E3%83%99%E3%83%AB%E9%A0%86%E8%B5%B0%E6%9F%BB%22%22%22%0A%20%20%20%20%23%20%E3%82%AD%E3%83%A5%E3%83%BC%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%97%E3%80%81%E3%83%AB%E3%83%BC%E3%83%88%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B%0A%20%20%20%20queue%3A%20deque%5BTreeNode%5D%20%3D%20deque%28%29%0A%20%20%20%20queue.append%28root%29%0A%20%20%20%20%23%20%E8%B5%B0%E6%9F%BB%E9%A0%86%E5%BA%8F%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20while%20queue%3A%0A%20%20%20%20%20%20%20%20node%3A%20TreeNode%20%3D%20queue.popleft%28%29%20%20%23%20%E3%83%87%E3%82%AD%E3%83%A5%E3%83%BC%0A%20%20%20%20%20%20%20%20res.append%28node.val%29%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E5%80%A4%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20if%20node.left%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.left%29%20%20%23%20%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%AB%E8%BF%BD%E5%8A%A0%0A%20%20%20%20%20%20%20%20if%20node.right%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20queue.append%28node.right%29%20%20%23%20%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%AB%E8%BF%BD%E5%8A%A0%0A%20%20%20%20return%20res%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9C%A8%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20%23%20%E3%81%93%E3%81%93%E3%81%A7%E3%81%AF%E3%80%81%E9%85%8D%E5%88%97%E3%81%8B%E3%82%89%E7%9B%B4%E6%8E%A5%E4%BA%8C%E5%88%86%E6%9C%A8%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B%E9%96%A2%E6%95%B0%E3%82%92%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1%2C%202%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E3%83%AC%E3%83%99%E3%83%AB%E9%A0%86%E8%B5%B0%E6%9F%BB%0A%20%20%20%20res%20%3D%20level_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E3%83%AC%E3%83%99%E3%83%AB%E9%A0%86%E8%B5%B0%E6%9F%BB%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E5%87%BA%E5%8A%9B%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B9%20%3D%20%22%2C%20res%29&cumulative=false&curInstr=127&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/pythontutor/chapter_tree/binary_tree_dfs.md b/ja/codes/pythontutor/chapter_tree/binary_tree_dfs.md new file mode 100644 index 000000000..04b21d815 --- /dev/null +++ b/ja/codes/pythontutor/chapter_tree/binary_tree_dfs.md @@ -0,0 +1,8 @@ + + + +https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%88%86%E6%9C%A8%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__%28self%2C%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E5%80%A4%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0Adef%20list_to_tree_dfs%28arr%3A%20list%5Bint%5D%2C%20i%3A%20int%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AB%E3%83%87%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8B%3A%20%E5%86%8D%E5%B8%B0%22%22%22%0A%20%20%20%20%23%20%E6%B7%BB%E5%AD%97%E3%81%8C%E9%85%8D%E5%88%97%E9%95%B7%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E3%81%8B%E3%80%81%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B%E8%A6%81%E7%B4%A0%E3%81%8C%20None%20%E3%81%AA%E3%82%89%E3%80%81None%20%E3%82%92%E8%BF%94%E3%81%99%0A%20%20%20%20if%20i%20%3C%200%20or%20i%20%3E%3D%20len%28arr%29%20or%20arr%5Bi%5D%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%20%20%20%20%23%20%E7%8F%BE%E5%9C%A8%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root%20%3D%20TreeNode%28arr%5Bi%5D%29%0A%20%20%20%20%23%20%E5%B7%A6%E5%8F%B3%E3%81%AE%E9%83%A8%E5%88%86%E6%9C%A8%E3%82%92%E5%86%8D%E5%B8%B0%E7%9A%84%E3%81%AB%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%0A%20%20%20%20root.left%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%201%29%0A%20%20%20%20root.right%20%3D%20list_to_tree_dfs%28arr%2C%202%20%2A%20i%20%2B%202%29%0A%20%20%20%20return%20root%0A%0Adef%20list_to_tree%28arr%3A%20list%5Bint%5D%29%20-%3E%20TreeNode%20%7C%20None%3A%0A%20%20%20%20%22%22%22%E3%83%AA%E3%82%B9%E3%83%88%E3%82%92%E4%BA%8C%E5%88%86%E6%9C%A8%E3%81%AB%E3%83%87%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%99%E3%82%8B%22%22%22%0A%20%20%20%20return%20list_to_tree_dfs%28arr%2C%200%29%0A%0A%0Adef%20pre_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E5%85%88%E8%A1%8C%E9%A0%86%E8%B5%B0%E6%9F%BB%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E9%A0%86%E5%BA%8F%EF%BC%9A%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%20-%3E%20%E5%B7%A6%E9%83%A8%E5%88%86%E6%9C%A8%20-%3E%20%E5%8F%B3%E9%83%A8%E5%88%86%E6%9C%A8%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20pre_order%28root%3Droot.left%29%0A%20%20%20%20pre_order%28root%3Droot.right%29%0A%0Adef%20in_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E4%B8%AD%E9%A0%86%E8%B5%B0%E6%9F%BB%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%84%AA%E5%85%88%E9%A0%86%3A%20%E5%B7%A6%E9%83%A8%E5%88%86%E6%9C%A8%20-%3E%20%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%20-%3E%20%E5%8F%B3%E9%83%A8%E5%88%86%E6%9C%A8%0A%20%20%20%20in_order%28root%3Droot.left%29%0A%20%20%20%20res.append%28root.val%29%0A%20%20%20%20in_order%28root%3Droot.right%29%0A%0Adef%20post_order%28root%3A%20TreeNode%20%7C%20None%29%3A%0A%20%20%20%20%22%22%22%E5%BE%8C%E9%A0%86%E8%B5%B0%E6%9F%BB%22%22%22%0A%20%20%20%20if%20root%20is%20None%3A%0A%20%20%20%20%20%20%20%20return%0A%20%20%20%20%23%20%E8%A8%AA%E5%95%8F%E5%84%AA%E5%85%88%E9%A0%86%3A%20%E5%B7%A6%E9%83%A8%E5%88%86%E6%9C%A8%20-%3E%20%E5%8F%B3%E9%83%A8%E5%88%86%E6%9C%A8%20-%3E%20%E6%A0%B9%E3%83%8E%E3%83%BC%E3%83%89%0A%20%20%20%20post_order%28root%3Droot.left%29%0A%20%20%20%20post_order%28root%3Droot.right%29%0A%20%20%20%20res.append%28root.val%29%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BA%8C%E5%88%86%E6%9C%A8%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20%23%20%E3%81%93%E3%81%93%E3%81%A7%E3%81%AF%E3%80%81%E9%85%8D%E5%88%97%E3%81%8B%E3%82%89%E7%9B%B4%E6%8E%A5%E4%BA%8C%E5%88%86%E6%9C%A8%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B%E9%96%A2%E6%95%B0%E3%82%92%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B%0A%20%20%20%20root%20%3D%20list_to_tree%28arr%3D%5B1%2C%202%2C%203%2C%204%2C%205%2C%206%2C%207%5D%29%0A%0A%20%20%20%20%23%20%E5%85%88%E8%A1%8C%E9%A0%86%E8%B5%B0%E6%9F%BB%0A%20%20%20%20res%20%3D%20%5B%5D%0A%20%20%20%20pre_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E5%85%88%E8%A1%8C%E9%A0%86%E8%B5%B0%E6%9F%BB%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E5%87%BA%E5%8A%9B%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B9%20%3D%20%22%2C%20res%29%0A%0A%20%20%20%20%23%20%E4%B8%AD%E9%A0%86%E8%B5%B0%E6%9F%BB%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20in_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E4%B8%AD%E9%96%93%E9%A0%86%E8%B5%B0%E6%9F%BB%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E5%87%BA%E5%8A%9B%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B9%20%3D%20%22%2C%20res%29%0A%0A%20%20%20%20%23%20%E5%BE%8C%E9%A0%86%E8%B5%B0%E6%9F%BB%0A%20%20%20%20res.clear%28%29%0A%20%20%20%20post_order%28root%29%0A%20%20%20%20print%28%22%5Cn%E5%BE%8C%E8%A1%8C%E9%A0%86%E8%B5%B0%E6%9F%BB%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E5%87%BA%E5%8A%9B%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B9%20%3D%20%22%2C%20res%29&cumulative=false&curInstr=129&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/codes/ruby/chapter_array_and_linkedlist/array.rb b/ja/codes/ruby/chapter_array_and_linkedlist/array.rb new file mode 100644 index 000000000..79dbaed3a --- /dev/null +++ b/ja/codes/ruby/chapter_array_and_linkedlist/array.rb @@ -0,0 +1,108 @@ +=begin +File: array.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 要素にランダムアクセス ### +def random_access(nums) + # 区間 [0, nums.length) からランダムに 1 つの数を選ぶ + random_index = Random.rand(0...nums.length) + + # ランダムな要素を取得して返す + nums[random_index] +end + + +### 配列長を拡張 ### +# Ruby の Array は動的配列であり、直接拡張できます +# 学習しやすいよう、本関数では Array を長さ不変の配列として扱います +def extend(nums, enlarge) + # 拡張後の長さを持つ配列を初期化する + res = Array.new(nums.length + enlarge, 0) + + # 元の配列の全要素を新しい配列にコピー + for i in 0...nums.length + res[i] = nums[i] + end + + # 拡張後の新しい配列を返す + res +end + +### 配列のインデックス index に要素 num を挿入 ### +def insert(nums, num, index) + # インデックス index 以降の全要素を 1 つ後ろへ移動する + for i in (nums.length - 1).downto(index + 1) + nums[i] = nums[i - 1] + end + + # index の要素に num を代入する + nums[index] = num +end + + +### インデックス index の要素を削除 ### +def remove(nums, index) + # インデックス index より後ろの全要素を 1 つ前へ移動する + for i in index...(nums.length - 1) + nums[i] = nums[i + 1] + end +end + +### 配列を走査 ### +def traverse(nums) + count = 0 + + # インデックスで配列を走査 + for i in 0...nums.length + count += nums[i] + end + + # 配列要素を直接走査 + for num in nums + count += num + end +end + +### 配列内の指定要素を検索 ### +def find(nums, target) + for i in 0...nums.length + return i if nums[i] == target + end + + -1 +end + + +### Driver Code ### +if __FILE__ == $0 + # 配列を初期化 + arr = Array.new(5, 0) + puts "配列 arr = #{arr}" + nums = [1, 3, 2, 5, 4] + puts "配列 nums = #{nums}" + + # ランダムアクセス + random_num = random_access(nums) + puts "nums からランダムな要素 #{random_num} を取得" + + # 長さを拡張 + nums = extend(nums, 3) + puts "配列の長さを 8 に拡張し、nums = #{nums}" + + # 要素を挿入する + insert(nums, 6, 3) + puts "インデックス 3 に数値 6 を挿入し、nums = #{nums}" + + # 要素を削除 + remove(nums, 2) + puts "インデックス 2 の要素を削除し、nums = #{nums}" + + # 配列を走査 + traverse(nums) + + # 要素を探索する + index = find(nums, 3) + puts "nums 内で要素 3 を検索し、インデックス = #{index}" +end diff --git a/ja/codes/ruby/chapter_array_and_linkedlist/linked_list.rb b/ja/codes/ruby/chapter_array_and_linkedlist/linked_list.rb new file mode 100644 index 000000000..1b25c137d --- /dev/null +++ b/ja/codes/ruby/chapter_array_and_linkedlist/linked_list.rb @@ -0,0 +1,83 @@ +=begin +File: linked_list.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' +require_relative '../utils/print_util' + +### 連結リストのノード n0 の後にノード _p を挿入 ### +# Ruby の `p` は組み込み関数で、`P` は定数なので、代わりに `_p` を使える +def insert(n0, _p) + n1 = n0.next + _p.next = n1 + n0.next = _p +end + +### 連結リストのノード n0 の直後のノードを削除 ### +def remove(n0) + return if n0.next.nil? + + # n0 -> remove_node -> n1 + remove_node = n0.next + n1 = remove_node.next + n0.next = n1 +end + +### 連結リスト内の index 番目のノードにアクセス ### +def access(head, index) + for i in 0...index + return nil if head.nil? + head = head.next + end + + head +end + +### 連結リストで値が target の最初のノードを探す ### +def find(head, target) + index = 0 + while head + return index if head.val == target + head = head.next + index += 1 + end + + -1 +end + +### Driver Code ### +if __FILE__ == $0 + # 連結リストを初期化する + # 各ノードを初期化する + n0 = ListNode.new(1) + n1 = ListNode.new(3) + n2 = ListNode.new(2) + n3 = ListNode.new(5) + n4 = ListNode.new(4) + # ノード間の参照を構築する + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + puts "初期化した連結リストは" + print_linked_list(n0) + + # ノードを挿入 + insert(n0, ListNode.new(0)) + print_linked_list n0 + + # ノードを削除 + remove(n0) + puts "ノード削除後の連結リストは" + print_linked_list(n0) + + # ノードにアクセス + node = access(n0, 3) + puts "連結リストのインデックス 3 にあるノードの値 = #{node.val}" + + # ノードを探索 + index = find(n0, 2) + puts "連結リスト内で値が 2 のノードのインデックス = #{index}" +end diff --git a/ja/codes/ruby/chapter_array_and_linkedlist/list.rb b/ja/codes/ruby/chapter_array_and_linkedlist/list.rb new file mode 100644 index 000000000..47326c3b7 --- /dev/null +++ b/ja/codes/ruby/chapter_array_and_linkedlist/list.rb @@ -0,0 +1,60 @@ +=begin +File: list.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### Driver Code ### +if __FILE__ == $0 + # リストを初期化 + nums = [1, 3, 2, 5, 4] + puts "リスト nums = #{nums}" + + # 要素にアクセス + num = nums[1] + puts "インデックス 1 の要素にアクセスし、num = #{num}" + + # 要素を更新 + nums[1] = 0 + puts "インデックス 1 の要素を 0 に更新し、nums = #{nums}" + + # リストを空にする + nums.clear + puts "リストを空にした後 nums = #{nums}" + + # 末尾に要素を追加 + nums << 1 + nums << 3 + nums << 2 + nums << 5 + nums << 4 + puts "要素追加後 nums = #{nums}" + + # 中間に要素を挿入 + nums.insert(3, 6) + puts "インデックス 3 に要素 6 を挿入し、nums = #{nums}" + + # 要素を削除 + nums.delete_at(3) + puts "インデックス 3 の要素を削除し、nums = #{nums}" + + # インデックスでリストを走査 + count = 0 + for i in 0...nums.length + count += nums[i] + end + + # リスト要素を直接走査 + count = 0 + nums.each do |x| + count += x + end + + # 2 つのリストを連結する + nums1 = [6, 8, 7, 10, 9] + nums += nums1 + puts "リスト nums1 を nums の後ろに連結し、nums = #{nums}" + + nums = nums.sort { |a, b| a <=> b } + puts "リストをソートした後 nums = #{nums}" +end diff --git a/ja/codes/ruby/chapter_array_and_linkedlist/my_list.rb b/ja/codes/ruby/chapter_array_and_linkedlist/my_list.rb new file mode 100644 index 000000000..b021e2700 --- /dev/null +++ b/ja/codes/ruby/chapter_array_and_linkedlist/my_list.rb @@ -0,0 +1,132 @@ +=begin +File: my_list.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### リストクラス ### +class MyList + attr_reader :size # リストの長さを取得(現在の要素数) + attr_reader :capacity # リスト容量を取得する + + ### コンストラクタ ### + def initialize + @capacity = 10 + @size = 0 + @extend_ratio = 2 + @arr = Array.new(capacity) + end + + ### 要素にアクセス ### + def get(index) + # インデックスが範囲外なら例外を送出する。以下同様 + raise IndexError, "インデックスが範囲外です" if index < 0 || index >= size + @arr[index] + end + + ### 要素にアクセス ### + def set(index, num) + raise IndexError, "インデックスが範囲外です" if index < 0 || index >= size + @arr[index] = num + end + + ### 末尾に要素を追加 ### + def add(num) + # 要素数が容量を超えると、拡張機構が発動する + extend_capacity if size == capacity + @arr[size] = num + + # 要素数を更新 + @size += 1 + end + + ### 途中に要素を挿入 ### + def insert(index, num) + raise IndexError, "インデックスが範囲外です" if index < 0 || index >= size + + # 要素数が容量を超えると、拡張機構が発動する + extend_capacity if size == capacity + + # index 以降の要素をすべて 1 つ後ろへずらす + for j in (size - 1).downto(index) + @arr[j + 1] = @arr[j] + end + @arr[index] = num + + # 要素数を更新 + @size += 1 + end + + ### 要素の削除 ### + def remove(index) + raise IndexError, "インデックスが範囲外です" if index < 0 || index >= size + num = @arr[index] + + # インデックス index より後の要素をすべて 1 つ前に移動する + for j in index...size + @arr[j] = @arr[j + 1] + end + + # 要素数を更新 + @size -= 1 + + # 削除された要素を返す + num + end + + ### リストの容量拡張 ### + def extend_capacity + # 元の配列の extend_ratio 倍の長さを持つ新しい配列を作成し、元の配列をコピーする + arr = @arr.dup + Array.new(capacity * (@extend_ratio - 1)) + # リストの容量を更新 + @capacity = arr.length + end + + ### リストを配列に変換 ### + def to_array + sz = size + # 有効長の範囲内のリスト要素のみを変換 + arr = Array.new(sz) + for i in 0...sz + arr[i] = get(i) + end + arr + end +end + +### Driver Code ### +if __FILE__ == $0 + # リストを初期化 + nums = MyList.new + + # 末尾に要素を追加 + nums.add(1) + nums.add(3) + nums.add(2) + nums.add(5) + nums.add(4) + puts "リスト nums = #{nums.to_array}、容量 = #{nums.capacity}、長さ = #{nums.size}" + + # 中間に要素を挿入 + nums.insert(3, 6) + puts "インデックス 3 に数値 6 を挿入し、nums = #{nums.to_array}" + + # 要素を削除 + nums.remove(3) + puts "インデックス 3 の要素を削除し、nums = #{nums.to_array}" + + # 要素にアクセス + num = nums.get(1) + puts "インデックス 1 の要素にアクセスし、num = #{num}" + + # 要素を更新 + nums.set(1, 0) + puts "インデックス 1 の要素を 0 に更新し、nums = #{nums.to_array}" + + # 拡張機構をテストする + for i in 0...10 + # i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する + nums.add(i) + end + puts "拡張後のリスト nums = #{nums.to_array}、容量 = #{nums.capacity}、長さ = #{nums.size}" +end diff --git a/ja/codes/ruby/chapter_backtracking/n_queens.rb b/ja/codes/ruby/chapter_backtracking/n_queens.rb new file mode 100644 index 000000000..0f4cad96b --- /dev/null +++ b/ja/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/ja/codes/ruby/chapter_backtracking/permutations_i.rb b/ja/codes/ruby/chapter_backtracking/permutations_i.rb new file mode 100644 index 000000000..c20245c0c --- /dev/null +++ b/ja/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/ja/codes/ruby/chapter_backtracking/permutations_ii.rb b/ja/codes/ruby/chapter_backtracking/permutations_ii.rb new file mode 100644 index 000000000..7c75c02b5 --- /dev/null +++ b/ja/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/ja/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb b/ja/codes/ruby/chapter_backtracking/preorder_traversal_i_compact.rb new file mode 100644 index 000000000..ebaf4523c --- /dev/null +++ b/ja/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' + +### 前順走査:例題1 ### +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/ja/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb b/ja/codes/ruby/chapter_backtracking/preorder_traversal_ii_compact.rb new file mode 100644 index 000000000..790cf3a9f --- /dev/null +++ b/ja/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' + +### 前順走査:例題2 ### +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/ja/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb b/ja/codes/ruby/chapter_backtracking/preorder_traversal_iii_compact.rb new file mode 100644 index 000000000..baa5a86c6 --- /dev/null +++ b/ja/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' + +### 前順走査:例題3 ### +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/ja/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb b/ja/codes/ruby/chapter_backtracking/preorder_traversal_iii_template.rb new file mode 100644 index 000000000..d760bb829 --- /dev/null +++ b/ja/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 + +### バックトラッキング法:例題3 ### +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/ja/codes/ruby/chapter_backtracking/subset_sum_i.rb b/ja/codes/ruby/chapter_backtracking/subset_sum_i.rb new file mode 100644 index 000000000..fa251dd69 --- /dev/null +++ b/ja/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 + # すべての選択肢を走査 + # 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + for i in start...choices.length + # 枝刈り1:部分集合の和が 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/ja/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb b/ja/codes/ruby/chapter_backtracking/subset_sum_i_naive.rb new file mode 100644 index 000000000..2b3445f62 --- /dev/null +++ b/ja/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/ja/codes/ruby/chapter_backtracking/subset_sum_ii.rb b/ja/codes/ruby/chapter_backtracking/subset_sum_ii.rb new file mode 100644 index 000000000..81cb8fc34 --- /dev/null +++ b/ja/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 + + # すべての選択肢を走査 + # 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + # 枝刈り 3: start から走査し、同じ要素の重複選択を避ける + for i in start...choices.length + # 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + # 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため + break if target - choices[i] < 0 + # 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする + 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/ja/codes/ruby/chapter_computational_complexity/iteration.rb b/ja/codes/ruby/chapter_computational_complexity/iteration.rb new file mode 100644 index 000000000..059d0c638 --- /dev/null +++ b/ja/codes/ruby/chapter_computational_complexity/iteration.rb @@ -0,0 +1,79 @@ +=begin +File: iteration.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com), Cy (9738314@gmail.com) +=end + +### for ループ ### +def for_loop(n) + res = 0 + + # 1, 2, ..., n-1, n を順に加算する + for i in 1..n + res += i + end + + res +end + +### while ループ ### +def while_loop(n) + res = 0 + i = 1 # 条件変数を初期化する + + # 1, 2, ..., n-1, n を順に加算する + while i <= n + res += i + i += 1 # 条件変数を更新する + end + + res +end + +# ## while ループ(2 回更新)### +def while_loop_ii(n) + res = 0 + i = 1 # 条件変数を初期化する + + # 1, 4, 10, ... を順に加算する + while i <= n + res += i + # 条件変数を更新する + i += 1 + i *= 2 + end + + res +end + +### 二重 for ループ ### +def nested_for_loop(n) + res = "" + + # i = 1, 2, ..., n-1, n とループする + for i in 1..n + # j = 1, 2, ..., n-1, n とループする + for j in 1..n + res += "(#{i}, #{j}), " + end + end + + res +end + +### Driver Code ### +if __FILE__ == $0 + n = 5 + + res = for_loop(n) + puts "\nfor ループの合計結果 res = #{res}" + + res = while_loop(n) + puts "\nwhile ループの合計結果 res = #{res}" + + res = while_loop_ii(n) + puts "\nwhile ループ(2 回更新)の合計結果 res = #{res}" + + res = nested_for_loop(n) + puts "\n二重 for ループの走査結果 #{res}" +end diff --git a/ja/codes/ruby/chapter_computational_complexity/recursion.rb b/ja/codes/ruby/chapter_computational_complexity/recursion.rb new file mode 100644 index 000000000..b6f51a42b --- /dev/null +++ b/ja/codes/ruby/chapter_computational_complexity/recursion.rb @@ -0,0 +1,70 @@ +=begin +File: recursion.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 再帰 ### +def recur(n) + # 終了条件 + return 1 if n == 1 + # 再帰:再帰呼び出し + res = recur(n - 1) + # 帰りがけ:結果を返す + n + res +end + +### 反復で再帰をシミュレート ### +def for_loop_recur(n) + # 明示的なスタックを使ってシステムコールスタックを模擬する + stack = [] + res = 0 + + # 再帰:再帰呼び出し + for i in n.downto(0) + # 「スタックへのプッシュ」で「再帰」を模擬する + stack << i + end + # 帰りがけ:結果を返す + while !stack.empty? + res += stack.pop + end + + # res = 1+2+3+...+n + res +end + +### 末尾再帰 ### +def tail_recur(n, res) + # 終了条件 + return res if n == 0 + # 末尾再帰呼び出し + tail_recur(n - 1, res + n) +end + +### フィボナッチ数列:再帰 ### +def fib(n) + # 終了条件 f(1) = 0, f(2) = 1 + return n - 1 if n == 1 || n == 2 + # f(n) = f(n-1) + f(n-2) を再帰的に呼び出す + res = fib(n - 1) + fib(n - 2) + # 結果 f(n) を返す + res +end + +### Driver Code ### +if __FILE__ == $0 + n = 5 + + res = recur(n) + puts "\n再帰関数の合計結果 res = #{res}" + + res = for_loop_recur(n) + puts "\n反復で再帰をシミュレートした合計結果 res = #{res}" + + res = tail_recur(n, 0) + puts "\n末尾再帰関数の合計結果 res = #{res}" + + res = fib(n) + puts "\nフィボナッチ数列の第 #{n} 項は #{res}" +end diff --git a/ja/codes/ruby/chapter_computational_complexity/space_complexity.rb b/ja/codes/ruby/chapter_computational_complexity/space_complexity.rb new file mode 100644 index 000000000..f82e63e70 --- /dev/null +++ b/ja/codes/ruby/chapter_computational_complexity/space_complexity.rb @@ -0,0 +1,92 @@ +=begin +File: space_complexity.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 関数 ### +def function + # 何らかの処理を行う + 0 +end + +### 定数階 ### +def constant(n) + # 定数、変数、オブジェクトは O(1) の空間を占める + a = 0 + nums = [0] * 10000 + node = ListNode.new + + # ループ内の変数は O(1) の空間を占める + (0...n).each { c = 0 } + # ループ内の関数は O(1) の空間を占める + (0...n).each { function } +end + +### 線形階 ### +def linear(n) + # 長さ n のリストは O(n) の空間を使用 + nums = Array.new(n, 0) + + # 長さ n のハッシュテーブルは O(n) の空間を使用 + hmap = {} + for i in 0...n + hmap[i] = i.to_s + end +end + +# ## 線形階(再帰実装)### +def linear_recur(n) + puts "再帰 n = #{n}" + return if n == 1 + linear_recur(n - 1) +end + +### 平方階 ### +def quadratic(n) + # 二次元リストは O(n^2) の空間を使用 + Array.new(n) { Array.new(n, 0) } +end + +# ## 平方階(再帰実装)### +def quadratic_recur(n) + return 0 unless n > 0 + + # 配列 nums の長さは n, n-1, ..., 2, 1 + nums = Array.new(n, 0) + quadratic_recur(n - 1) +end + +# ## 指数階(満二分木を構築)### +def build_tree(n) + return if n == 0 + + TreeNode.new.tap do |root| + root.left = build_tree(n - 1) + root.right = build_tree(n - 1) + end +end + +### Driver Code ### +if __FILE__ == $0 + n = 5 + + # 定数階 + constant(n) + + # 線形階 + linear(n) + linear_recur(n) + + # 二乗階 + quadratic(n) + quadratic_recur(n) + + # 指数オーダー + root = build_tree(n) + print_tree(root) +end diff --git a/ja/codes/ruby/chapter_computational_complexity/time_complexity.rb b/ja/codes/ruby/chapter_computational_complexity/time_complexity.rb new file mode 100644 index 000000000..e23ed76ab --- /dev/null +++ b/ja/codes/ruby/chapter_computational_complexity/time_complexity.rb @@ -0,0 +1,165 @@ +=begin +File: time_complexity.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 定数階 ### +def constant(n) + count = 0 + size = 100000 + + (0...size).each { count += 1 } + + count +end + +### 線形階 ### +def linear(n) + count = 0 + (0...n).each { count += 1 } + count +end + +# ## 線形階(配列を走査)### +def array_traversal(nums) + count = 0 + + # ループ回数は配列長に比例する + for num in nums + count += 1 + end + + count +end + +### 平方階 ### +def quadratic(n) + count = 0 + + # ループ回数はデータサイズ n の二乗に比例する + for i in 0...n + for j in 0...n + count += 1 + end + end + + count +end + +# ## 平方階(バブルソート)### +def bubble_sort(nums) + count = 0 # カウンタ + + # 外側のループ:未ソート区間は [0, i] + for i in (nums.length - 1).downto(0) + # 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for j in 0...i + if nums[j] > nums[j + 1] + # nums[j] と nums[j + 1] を交換 + tmp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = tmp + count += 3 # 要素交換には 3 回の単位操作が含まれる + end + end + end + + count +end + +# ## 指数階(ループ実装)### +def exponential(n) + count, base = 0, 1 + + # 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する + (0...n).each do + (0...base).each { count += 1 } + base *= 2 + end + + # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + count +end + +# ## 指数階(再帰実装)### +def exp_recur(n) + return 1 if n == 1 + exp_recur(n - 1) + exp_recur(n - 1) + 1 +end + +# ## 対数階(ループ実装)### +def logarithmic(n) + count = 0 + + while n > 1 + n /= 2 + count += 1 + end + + count +end + +# ## 対数階(再帰実装)### +def log_recur(n) + return 0 unless n > 1 + log_recur(n / 2) + 1 +end + +### 線形対数時間 ### +def linear_log_recur(n) + return 1 unless n > 1 + + count = linear_log_recur(n / 2) + linear_log_recur(n / 2) + (0...n).each { count += 1 } + + count +end + +# ## 階乗階(再帰実装)### +def factorial_recur(n) + return 1 if n == 0 + + count = 0 + # 1個から n 個に分裂 + (0...n).each { count += factorial_recur(n - 1) } + + count +end + +### Driver Code ### +if __FILE__ == $0 + # n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる + n = 8 + puts "入力データサイズ n = #{n}" + + count = constant(n) + puts "定数時間の操作回数 = #{count}" + + count = linear(n) + puts "線形時間の操作回数 = #{count}" + count = array_traversal(Array.new(n, 0)) + puts "線形時間(配列走査)の操作回数 = #{count}" + + count = quadratic(n) + puts "二次時間の操作回数 = #{count}" + nums = Array.new(n) { |i| n - i } # [n, n-1, ..., 2, 1] + count = bubble_sort(nums) + puts "二次時間(バブルソート)の操作回数 = #{count}" + + count = exponential(n) + puts "指数時間(ループ実装)の操作回数 = #{count}" + count = exp_recur(n) + puts "指数時間(再帰実装)の操作回数 = #{count}" + + count = logarithmic(n) + puts "対数時間(ループ実装)の操作回数 = #{count}" + count = log_recur(n) + puts "対数時間(再帰実装)の操作回数 = #{count}" + + count = linear_log_recur(n) + puts "線形対数時間(再帰実装)の操作回数 = #{count}" + + count = factorial_recur(n) + puts "階乗時間(再帰実装)の操作回数 = #{count}" +end diff --git a/ja/codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb b/ja/codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb new file mode 100644 index 000000000..ab0a845be --- /dev/null +++ b/ja/codes/ruby/chapter_computational_complexity/worst_best_time_complexity.rb @@ -0,0 +1,35 @@ +=begin +File: worst_best_time_complexity.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 1, 2, ..., n を要素とする配列を生成し、順序をシャッフルする ### +def random_numbers(n) + # 配列 nums =: 1, 2, 3, ..., n を生成する + nums = Array.new(n) { |i| i + 1 } + # 配列要素をランダムにシャッフル + nums.shuffle! +end + +### 配列 nums 内の数値 1 のインデックスを探す ### +def find_one(nums) + for i in 0...nums.length + # 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる + # 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる + return i if nums[i] == 1 + end + + -1 +end + +### Driver Code ### +if __FILE__ == $0 + for i in 0...10 + n = 100 + nums = random_numbers(n) + index = find_one(nums) + puts "\n配列 [ 1, 2, ..., n ] をシャッフルした後 = #{nums}" + puts "数字 1 のインデックスは #{index}" + end +end diff --git a/ja/codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb b/ja/codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb new file mode 100644 index 000000000..94ebd2a19 --- /dev/null +++ b/ja/codes/ruby/chapter_divide_and_conquer/binary_search_recur.rb @@ -0,0 +1,42 @@ +=begin +File: binary_search_recur.rb +Created Time: 2024-05-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 二分探索: 問題 f(i, j) ### +def dfs(nums, target, i, j) + # 区間が空なら対象要素は存在しないので -1 を返す + return -1 if i > j + + # 中点インデックス m を計算 + m = (i + j) / 2 + + if nums[m] < target + # 部分問題 f(m+1, j) を再帰的に解く + return dfs(nums, target, m + 1, j) + elsif nums[m] > target + # 部分問題 f(i, m-1) を再帰的に解く + return dfs(nums, target, i, m - 1) + else + # 目標要素が見つかったらそのインデックスを返す + return m + end +end + +### 二分探索 ### +def binary_search(nums, target) + n = nums.length + # 問題 f(0, n-1) を解く + dfs(nums, target, 0, n - 1) +end + +### Driver Code ### +if __FILE__ == $0 + target = 6 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + # 二分探索(両閉区間) + index = binary_search(nums, target) + puts "対象要素 6 のインデックス = #{index}" +end diff --git a/ja/codes/ruby/chapter_divide_and_conquer/build_tree.rb b/ja/codes/ruby/chapter_divide_and_conquer/build_tree.rb new file mode 100644 index 000000000..f95531ed7 --- /dev/null +++ b/ja/codes/ruby/chapter_divide_and_conquer/build_tree.rb @@ -0,0 +1,46 @@ +=begin +File: build_tree.rb +Created Time: 2024-05-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 二分木を構築:分割統治 ### +def dfs(preorder, inorder_map, i, l, r) + # 部分木区間が空なら終了する + return if r - l < 0 + + # ルートノードを初期化する + root = TreeNode.new(preorder[i]) + # m を求めて左右部分木を分割する + m = inorder_map[preorder[i]] + # 部分問題:左部分木を構築する + root.left = dfs(preorder, inorder_map, i + 1, l, m - 1) + # 部分問題:右部分木を構築する + root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r) + + # 根ノードを返す + root +end + +### 二分木を構築 ### +def build_tree(preorder, inorder) + # inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する + inorder_map = {} + inorder.each_with_index { |val, i| inorder_map[val] = i } + dfs(preorder, inorder_map, 0, 0, inorder.length - 1) +end + +### Driver Code ### +if __FILE__ == $0 + preorder = [3, 9, 2, 1, 7] + inorder = [9, 3, 1, 2, 7] + puts "前順走査 = #{preorder}" + puts "中順走査 = #{inorder}" + + root = build_tree(preorder, inorder) + puts "構築した二分木は:" + print_tree(root) +end diff --git a/ja/codes/ruby/chapter_divide_and_conquer/hanota.rb b/ja/codes/ruby/chapter_divide_and_conquer/hanota.rb new file mode 100644 index 000000000..128476aee --- /dev/null +++ b/ja/codes/ruby/chapter_divide_and_conquer/hanota.rb @@ -0,0 +1,55 @@ +=begin +File: hanota.rb +Created Time: 2024-05-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 円盤を1枚移動 ### +def move(src, tar) + # src の上から円盤を1枚取り出す + pan = src.pop + # 円盤を tar の上に置く + tar << pan +end + +### ハノイの塔 f(i) を解く ### +def dfs(i, src, buf, tar) + # src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す + if i == 1 + move(src, tar) + return + end + + # 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す + dfs(i - 1, src, tar, buf) + # 部分問題 f(1):src に残る 1 枚の円盤を tar に移す + move(src, tar) + # 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す + dfs(i - 1, buf, src, tar) +end + +### ハノイの塔を解く ### +def solve_hanota(_A, _B, _C) + n = _A.length + # A の上から n 枚の円盤を B を介して C へ移す + dfs(n, _A, _B, _C) +end + +### Driver Code ### +if __FILE__ == $0 + # リスト末尾が柱の頂上 + A = [5, 4, 3, 2, 1] + B = [] + C = [] + puts "初期状態:" + puts "A = #{A}" + puts "B = #{B}" + puts "C = #{C}" + + solve_hanota(A, B, C) + + puts "円盤の移動完了後:" + puts "A = #{A}" + puts "B = #{B}" + puts "C = #{C}" +end diff --git a/ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb b/ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb new file mode 100644 index 000000000..030b1a615 --- /dev/null +++ b/ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_backtrack.rb @@ -0,0 +1,37 @@ +=begin +File: climbing_stairs_backtrack.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### バックトラッキング ### +def backtrack(choices, state, n, res) + # 第 n 段に到達したら、方法数を 1 増やす + res[0] += 1 if state == n + # すべての選択肢を走査 + for choice in choices + # 枝刈り: 第 n 段を超えないようにする + next if state + choice > n + + # 試行: 選択を行い、状態を更新 + backtrack(choices, state + choice, n, res) + end + # バックトラック +end + +### 階段登り:バックトラッキング ### +def climbing_stairs_backtrack(n) + choices = [1, 2] # 1 段または 2 段上ることを選べる + state = 0 # 第 0 段から上り始める + res = [0] # res[0] を使って方法数を記録する + backtrack(choices, state, n, res) + res.first +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_backtrack(n) + puts "#{n} 段の階段を上る方法は全部で #{res} 通り" +end diff --git a/ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb b/ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb new file mode 100644 index 000000000..d17ba13fe --- /dev/null +++ b/ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_constraint_dp.rb @@ -0,0 +1,31 @@ +=begin +File: climbing_stairs_constraint_dp.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 制約付き階段登り:動的計画法 ### +def climbing_stairs_constraint_dp(n) + return 1 if n == 1 || n == 2 + + # 部分問題の解を保存するために dp テーブルを初期化 + dp = Array.new(n + 1) { Array.new(3, 0) } + # 初期状態:最小部分問題の解をあらかじめ設定 + dp[1][1], dp[1][2] = 1, 0 + dp[2][1], dp[2][2] = 0, 1 + # 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for i in 3...(n + 1) + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + end + + dp[n][1] + dp[n][2] +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_constraint_dp(n) + puts "#{n} 段の階段を上る方法は全部で #{res} 通り" +end diff --git a/ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb b/ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb new file mode 100644 index 000000000..2b936d4d1 --- /dev/null +++ b/ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs.rb @@ -0,0 +1,26 @@ +=begin +File: climbing_stairs_dfs.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 探索 ### +def dfs(i) + # dp[1] と dp[2] は既知なので返す + return i if i == 1 || i == 2 + # dp[i] = dp[i-1] + dp[i-2] + dfs(i - 1) + dfs(i - 2) +end + +### 階段登り:探索 ### +def climbing_stairs_dfs(n) + dfs(n) +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_dfs(n) + puts "#{n} 段の階段を上る方法は全部で #{res} 通り" +end diff --git a/ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb b/ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb new file mode 100644 index 000000000..1562b22cf --- /dev/null +++ b/ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_dfs_mem.rb @@ -0,0 +1,33 @@ +=begin +File: climbing_stairs_dfs_mem.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### メモ化探索 ### +def dfs(i, mem) + # dp[1] と dp[2] は既知なので返す + return i if i == 1 || i == 2 + # dp[i] の記録があれば、それをそのまま返す + return mem[i] if mem[i] != -1 + + # dp[i] = dp[i-1] + dp[i-2] + count = dfs(i - 1, mem) + dfs(i - 2, mem) + # dp[i] を記録する + mem[i] = count +end + +### 階段登り:メモ化探索 ### +def climbing_stairs_dfs_mem(n) + # mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す + mem = Array.new(n + 1, -1) + dfs(n, mem) +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_dfs_mem(n) + puts "#{n} 段の階段を上る方法は全部で #{res} 通り" +end diff --git a/ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb b/ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb new file mode 100644 index 000000000..0a0211126 --- /dev/null +++ b/ja/codes/ruby/chapter_dynamic_programming/climbing_stairs_dp.rb @@ -0,0 +1,40 @@ +=begin +File: climbing_stairs_dp.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 階段登り:動的計画法 ### +def climbing_stairs_dp(n) + return n if n == 1 || n == 2 + + # 部分問題の解を保存するために dp テーブルを初期化 + dp = Array.new(n + 1, 0) + # 初期状態:最小部分問題の解をあらかじめ設定 + dp[1], dp[2] = 1, 2 + # 状態遷移:小さい部分問題から大きい部分問題へ順に解く + (3...(n + 1)).each { |i| dp[i] = dp[i - 1] + dp[i - 2] } + + dp[n] +end + +### 階段登り:空間最適化後の動的計画法 ### +def climbing_stairs_dp_comp(n) + return n if n == 1 || n == 2 + + a, b = 1, 2 + (3...(n + 1)).each { a, b = b, a + b } + + b +end + +### Driver Code ### +if __FILE__ == $0 + n = 9 + + res = climbing_stairs_dp(n) + puts "#{n} 段の階段を上る方法は全部で #{res} 通り" + + res = climbing_stairs_dp_comp(n) + puts "#{n} 段の階段を上る方法は全部で #{res} 通り" +end diff --git a/ja/codes/ruby/chapter_dynamic_programming/coin_change.rb b/ja/codes/ruby/chapter_dynamic_programming/coin_change.rb new file mode 100644 index 000000000..f68d2bf9b --- /dev/null +++ b/ja/codes/ruby/chapter_dynamic_programming/coin_change.rb @@ -0,0 +1,65 @@ +=begin +File: coin_change.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### コイン両替:動的計画法 ### +def coin_change_dp(coins, amt) + n = coins.length + _MAX = amt + 1 + # dp テーブルを初期化 + dp = Array.new(n + 1) { Array.new(amt + 1, 0) } + # 状態遷移:先頭行と先頭列 + (1...(amt + 1)).each { |a| dp[0][a] = _MAX } + # 状態遷移: 残りの行と列 + for i in 1...(n + 1) + for a in 1...(amt + 1) + if coins[i - 1] > a + # 目標金額を超えるなら硬貨 i は選ばない + dp[i][a] = dp[i - 1][a] + else + # 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[i][a] = [dp[i - 1][a], dp[i][a - coins[i - 1]] + 1].min + end + end + end + dp[n][amt] != _MAX ? dp[n][amt] : -1 +end + +### コイン両替:空間最適化した動的計画法 ### +def coin_change_dp_comp(coins, amt) + n = coins.length + _MAX = amt + 1 + # dp テーブルを初期化 + dp = Array.new(amt + 1, _MAX) + dp[0] = 0 + # 状態遷移 + for i in 1...(n + 1) + # 順方向に走査する + for a in 1...(amt + 1) + if coins[i - 1] > a + # 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a] + else + # 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[a] = [dp[a], dp[a - coins[i - 1]] + 1].min + end + end + end + dp[amt] != _MAX ? dp[amt] : -1 +end + +### Driver Code ### +if __FILE__ == $0 + coins = [1, 2, 5] + amt = 4 + + # 動的計画法 + res = coin_change_dp(coins, amt) + puts "目標金額にするために必要な最小硬貨枚数は #{res}" + + # 空間最適化後の動的計画法 + res = coin_change_dp_comp(coins, amt) + puts "目標金額にするために必要な最小硬貨枚数は #{res}" +end diff --git a/ja/codes/ruby/chapter_dynamic_programming/coin_change_ii.rb b/ja/codes/ruby/chapter_dynamic_programming/coin_change_ii.rb new file mode 100644 index 000000000..c05b8f01a --- /dev/null +++ b/ja/codes/ruby/chapter_dynamic_programming/coin_change_ii.rb @@ -0,0 +1,63 @@ +=begin +File: coin_change_ii.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### コイン両替 II:動的計画法 ### +def coin_change_ii_dp(coins, amt) + n = coins.length + # dp テーブルを初期化 + dp = Array.new(n + 1) { Array.new(amt + 1, 0) } + # 先頭列を初期化する + (0...(n + 1)).each { |i| dp[i][0] = 1 } + # 状態遷移 + for i in 1...(n + 1) + for a in 1...(amt + 1) + if coins[i - 1] > a + # 目標金額を超えるなら硬貨 i は選ばない + dp[i][a] = dp[i - 1][a] + else + # コイン i を選ばない場合と選ぶ場合の和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] + end + end + end + dp[n][amt] +end + +### コイン両替 II:空間最適化した動的計画法 ### +def coin_change_ii_dp_comp(coins, amt) + n = coins.length + # dp テーブルを初期化 + dp = Array.new(amt + 1, 0) + dp[0] = 1 + # 状態遷移 + for i in 1...(n + 1) + # 順方向に走査する + for a in 1...(amt + 1) + if coins[i - 1] > a + # 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a] + else + # コイン i を選ばない場合と選ぶ場合の和 + dp[a] = dp[a] + dp[a - coins[i - 1]] + end + end + end + dp[amt] +end + +### Driver Code ### +if __FILE__ == $0 + coins = [1, 2, 5] + amt = 5 + + # 動的計画法 + res = coin_change_ii_dp(coins, amt) + puts "目標金額を作る硬貨の組み合わせ数は #{res}" + + # 空間最適化後の動的計画法 + res = coin_change_ii_dp_comp(coins, amt) + puts "目標金額を作る硬貨の組み合わせ数は #{res}" +end diff --git a/ja/codes/ruby/chapter_dynamic_programming/edit_distance.rb b/ja/codes/ruby/chapter_dynamic_programming/edit_distance.rb new file mode 100644 index 000000000..855203438 --- /dev/null +++ b/ja/codes/ruby/chapter_dynamic_programming/edit_distance.rb @@ -0,0 +1,115 @@ +=begin +File: edit_distance.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 編集距離:総当たり探索 ### +def edit_distance_dfs(s, t, i, j) + # s と t がともに空なら 0 を返す + return 0 if i == 0 && j == 0 + # s が空なら t の長さを返す + return j if i == 0 + # t が空なら s の長さを返す + return i if j == 0 + # 2 つの文字が等しければ、その 2 文字をそのままスキップする + return edit_distance_dfs(s, t, i - 1, j - 1) if s[i - 1] == t[j - 1] + # 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + insert = edit_distance_dfs(s, t, i, j - 1) + delete = edit_distance_dfs(s, t, i - 1, j) + replace = edit_distance_dfs(s, t, i - 1, j - 1) + # 最小編集回数を返す + [insert, delete, replace].min + 1 +end + +def edit_distance_dfs_mem(s, t, mem, i, j) + # s と t がともに空なら 0 を返す + return 0 if i == 0 && j == 0 + # s が空なら t の長さを返す + return j if i == 0 + # t が空なら s の長さを返す + return i if j == 0 + # 記録済みなら、それをそのまま返す + return mem[i][j] if mem[i][j] != -1 + # 2 つの文字が等しければ、その 2 文字をそのままスキップする + return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) if s[i - 1] == t[j - 1] + # 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + insert = edit_distance_dfs_mem(s, t, mem, i, j - 1) + delete = edit_distance_dfs_mem(s, t, mem, i - 1, j) + replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1) + # 最小編集回数を記録して返す + mem[i][j] = [insert, delete, replace].min + 1 +end + +### 編集距離:動的計画法 ### +def edit_distance_dp(s, t) + n, m = s.length, t.length + dp = Array.new(n + 1) { Array.new(m + 1, 0) } + # 状態遷移:先頭行と先頭列 + (1...(n + 1)).each { |i| dp[i][0] = i } + (1...(m + 1)).each { |j| dp[0][j] = j } + # 状態遷移: 残りの行と列 + for i in 1...(n + 1) + for j in 1...(m +1) + if s[i - 1] == t[j - 1] + # 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[i][j] = dp[i - 1][j - 1] + else + # 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[i][j] = [dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]].min + 1 + end + end + end + dp[n][m] +end + +### 編集距離:空間最適化した動的計画法 ### +def edit_distance_dp_comp(s, t) + n, m = s.length, t.length + dp = Array.new(m + 1, 0) + # 状態遷移:先頭行 + (1...(m + 1)).each { |j| dp[j] = j } + # 状態遷移:残りの行 + for i in 1...(n + 1) + # 状態遷移:先頭列 + leftup = dp.first # dp[i-1, j-1] を一時保存する + dp[0] += 1 + # 状態遷移:残りの列 + for j in 1...(m + 1) + temp = dp[j] + if s[i - 1] == t[j - 1] + # 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[j] = leftup + else + # 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[j] = [dp[j - 1], dp[j], leftup].min + 1 + end + leftup = temp # 次の反復の dp[i-1, j-1] に更新する + end + end + dp[m] +end + +### Driver Code ### +if __FILE__ == $0 + s = 'bag' + t = 'pack' + n, m = s.length, t.length + + # 全探索 + res = edit_distance_dfs(s, t, n, m) + puts "#{s} を #{t} に変更するには最小で #{res} 回の編集が必要" + + # メモ化探索 + mem = Array.new(n + 1) { Array.new(m + 1, -1) } + res = edit_distance_dfs_mem(s, t, mem, n, m) + puts "#{s} を #{t} に変更するには最小で #{res} 回の編集が必要" + + # 動的計画法 + res = edit_distance_dp(s, t) + puts "#{s} を #{t} に変更するには最小で #{res} 回の編集が必要" + + # 空間最適化後の動的計画法 + res = edit_distance_dp_comp(s, t) + puts "#{s} を #{t} に変更するには最小で #{res} 回の編集が必要" +end diff --git a/ja/codes/ruby/chapter_dynamic_programming/knapsack.rb b/ja/codes/ruby/chapter_dynamic_programming/knapsack.rb new file mode 100644 index 000000000..d33a9aa20 --- /dev/null +++ b/ja/codes/ruby/chapter_dynamic_programming/knapsack.rb @@ -0,0 +1,99 @@ +=begin +File: knapsack.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 0-1 ナップサック: 全探索 ### +def knapsack_dfs(wgt, val, i, c) + # すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + return 0 if i == 0 || c == 0 + # ナップサック容量を超える場合は、入れない選択しかできない + return knapsack_dfs(wgt, val, i - 1, c) if wgt[i - 1] > c + # 品物 i を入れない場合と入れる場合の最大価値を計算する + no = knapsack_dfs(wgt, val, i - 1, c) + yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] + # 2つの案のうち価値が大きいほうを返す + [no, yes].max +end + +### 0-1 ナップサック: メモ化探索 ### +def knapsack_dfs_mem(wgt, val, mem, i, c) + # すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + return 0 if i == 0 || c == 0 + # 既に記録があればそのまま返す + return mem[i][c] if mem[i][c] != -1 + # ナップサック容量を超える場合は、入れない選択しかできない + return knapsack_dfs_mem(wgt, val, mem, i - 1, c) if wgt[i - 1] > c + # 品物 i を入れない場合と入れる場合の最大価値を計算する + no = knapsack_dfs_mem(wgt, val, mem, i - 1, c) + yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1] + # 2 つの案のうち価値が大きい方を記録して返す + mem[i][c] = [no, yes].max +end + +### 0-1 ナップサック: 動的計画法 ### +def knapsack_dp(wgt, val, cap) + n = wgt.length + # dp テーブルを初期化 + dp = Array.new(n + 1) { Array.new(cap + 1, 0) } + # 状態遷移 + for i in 1...(n + 1) + for c in 1...(cap + 1) + if wgt[i - 1] > c + # ナップサック容量を超えるなら品物 i は選ばない + dp[i][c] = dp[i - 1][c] + else + # 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i][c] = [dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]].max + end + end + end + dp[n][cap] +end + +### 0-1 ナップサック: 空間最適化後の動的計画法 ### +def knapsack_dp_comp(wgt, val, cap) + n = wgt.length + # dp テーブルを初期化 + dp = Array.new(cap + 1, 0) + # 状態遷移 + for i in 1...(n + 1) + # 逆順に走査する + for c in cap.downto(1) + if wgt[i - 1] > c + # ナップサック容量を超えるなら品物 i は選ばない + dp[c] = dp[c] + else + # 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max + end + end + end + dp[cap] +end + +### Driver Code ### +if __FILE__ == $0 + wgt = [10, 20, 30, 40, 50] + val = [50, 120, 150, 210, 240] + cap = 50 + n = wgt.length + + # 全探索 + res = knapsack_dfs(wgt, val, n, cap) + puts "ナップサック容量を超えない最大価値は #{res}" + + # メモ化探索 + mem = Array.new(n + 1) { Array.new(cap + 1, -1) } + res = knapsack_dfs_mem(wgt, val, mem, n, cap) + puts "ナップサック容量を超えない最大価値は #{res}" + + # 動的計画法 + res = knapsack_dp(wgt, val, cap) + puts "ナップサック容量を超えない最大価値は #{res}" + + # 空間最適化後の動的計画法 + res = knapsack_dp_comp(wgt, val, cap) + puts "ナップサック容量を超えない最大価値は #{res}" +end diff --git a/ja/codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb b/ja/codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb new file mode 100644 index 000000000..5b38a11f8 --- /dev/null +++ b/ja/codes/ruby/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rb @@ -0,0 +1,39 @@ +=begin +File: min_cost_climbing_stairs_dp.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 階段登りの最小コスト:動的計画法 ### +def min_cost_climbing_stairs_dp(cost) + n = cost.length - 1 + return cost[n] if n == 1 || n == 2 + # 部分問題の解を保存するために dp テーブルを初期化 + dp = Array.new(n + 1, 0) + # 初期状態:最小部分問題の解をあらかじめ設定 + dp[1], dp[2] = cost[1], cost[2] + # 状態遷移:小さい部分問題から大きい部分問題へ順に解く + (3...(n + 1)).each { |i| dp[i] = [dp[i - 1], dp[i - 2]].min + cost[i] } + dp[n] +end + +# 階段昇りの最小コスト:空間最適化後の動的計画法 +def min_cost_climbing_stairs_dp_comp(cost) + n = cost.length - 1 + return cost[n] if n == 1 || n == 2 + a, b = cost[1], cost[2] + (3...(n + 1)).each { |i| a, b = b, [a, b].min + cost[i] } + b +end + +### Driver Code ### +if __FILE__ == $0 + cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] + puts "入力された階段コストのリストは #{cost}" + + res = min_cost_climbing_stairs_dp(cost) + puts "階段を上り切る最小コストは #{res}" + + res = min_cost_climbing_stairs_dp_comp(cost) + puts "階段を上り切る最小コストは #{res}" +end diff --git a/ja/codes/ruby/chapter_dynamic_programming/min_path_sum.rb b/ja/codes/ruby/chapter_dynamic_programming/min_path_sum.rb new file mode 100644 index 000000000..4f8361cfb --- /dev/null +++ b/ja/codes/ruby/chapter_dynamic_programming/min_path_sum.rb @@ -0,0 +1,93 @@ +=begin +File: min_path_sum.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 最小経路和:全探索 ### +def min_path_sum_dfs(grid, i, j) + # 左上のセルなら探索を終了する + return grid[i][j] if i == 0 && j == 0 + # 行または列のインデックスが範囲外なら、コスト +∞ を返す + return Float::INFINITY if i < 0 || j < 0 + # 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する + up = min_path_sum_dfs(grid, i - 1, j) + left = min_path_sum_dfs(grid, i, j - 1) + # 左上隅から (i, j) までの最小経路コストを返す + [left, up].min + grid[i][j] +end + +### 最小経路和:メモ化探索 ### +def min_path_sum_dfs_mem(grid, mem, i, j) + # 左上のセルなら探索を終了する + return grid[0][0] if i == 0 && j == 0 + # 行または列のインデックスが範囲外なら、コスト +∞ を返す + return Float::INFINITY if i < 0 || j < 0 + # 既に記録があればそのまま返す + return mem[i][j] if mem[i][j] != -1 + # 左と上のセルからの最小経路コスト + up = min_path_sum_dfs_mem(grid, mem, i - 1, j) + left = min_path_sum_dfs_mem(grid, mem, i, j - 1) + # 左上から (i, j) までの最小経路コストを記録して返す + mem[i][j] = [left, up].min + grid[i][j] +end + +### 最小経路和:動的計画法 ### +def min_path_sum_dp(grid) + n, m = grid.length, grid.first.length + # dp テーブルを初期化 + dp = Array.new(n) { Array.new(m, 0) } + dp[0][0] = grid[0][0] + # 状態遷移:先頭行 + (1...m).each { |j| dp[0][j] = dp[0][j - 1] + grid[0][j] } + # 状態遷移:先頭列 + (1...n).each { |i| dp[i][0] = dp[i - 1][0] + grid[i][0] } + # 状態遷移: 残りの行と列 + for i in 1...n + for j in 1...m + dp[i][j] = [dp[i][j - 1], dp[i - 1][j]].min + grid[i][j] + end + end + dp[n -1][m -1] +end + +### 最小経路和:空間最適化後の動的計画法 ### +def min_path_sum_dp_comp(grid) + n, m = grid.length, grid.first.length + # dp テーブルを初期化 + dp = Array.new(m, 0) + # 状態遷移:先頭行 + dp[0] = grid[0][0] + (1...m).each { |j| dp[j] = dp[j - 1] + grid[0][j] } + # 状態遷移:残りの行 + for i in 1...n + # 状態遷移:先頭列 + dp[0] = dp[0] + grid[i][0] + # 状態遷移:残りの列 + (1...m).each { |j| dp[j] = [dp[j - 1], dp[j]].min + grid[i][j] } + end + dp[m - 1] +end + +### Driver Code ### +if __FILE__ == $0 + grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] + n, m = grid.length, grid.first.length + + # 全探索 + res = min_path_sum_dfs(grid, n - 1, m - 1) + puts "左上から右下までの最小経路和は #{res}" + + # メモ化探索 + mem = Array.new(n) { Array.new(m, - 1) } + res = min_path_sum_dfs_mem(grid, mem, n - 1, m -1) + puts "左上から右下までの最小経路和は #{res}" + + # 動的計画法 + res = min_path_sum_dp(grid) + puts "左上から右下までの最小経路和は #{res}" + + # 空間最適化後の動的計画法 + res = min_path_sum_dp_comp(grid) + puts "左上から右下までの最小経路和は #{res}" +end diff --git a/ja/codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb b/ja/codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb new file mode 100644 index 000000000..938841369 --- /dev/null +++ b/ja/codes/ruby/chapter_dynamic_programming/unbounded_knapsack.rb @@ -0,0 +1,61 @@ +=begin +File: unbounded_knapsack.rb +Created Time: 2024-05-29 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 完全ナップサック:動的計画法 ### +def unbounded_knapsack_dp(wgt, val, cap) + n = wgt.length + # dp テーブルを初期化 + dp = Array.new(n + 1) { Array.new(cap + 1, 0) } + # 状態遷移 + for i in 1...(n + 1) + for c in 1...(cap + 1) + if wgt[i - 1] > c + # ナップサック容量を超えるなら品物 i は選ばない + dp[i][c] = dp[i - 1][c] + else + # 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i][c] = [dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]].max + end + end + end + dp[n][cap] +end + +# ## 完全ナップサック: 空間最適化後の動的計画法 ##3 +def unbounded_knapsack_dp_comp(wgt, val, cap) + n = wgt.length + # dp テーブルを初期化 + dp = Array.new(cap + 1, 0) + # 状態遷移 + for i in 1...(n + 1) + # 順方向に走査する + for c in 1...(cap + 1) + if wgt[i -1] > c + # ナップサック容量を超えるなら品物 i は選ばない + dp[c] = dp[c] + else + # 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = [dp[c], dp[c - wgt[i - 1]] + val[i - 1]].max + end + end + end + dp[cap] +end + +### Driver Code ### +if __FILE__ == $0 + wgt = [1, 2, 3] + val = [5, 11, 15] + cap = 4 + + # 動的計画法 + res = unbounded_knapsack_dp(wgt, val, cap) + puts "ナップサック容量を超えない最大価値は #{res}" + + # 空間最適化後の動的計画法 + res = unbounded_knapsack_dp_comp(wgt, val, cap) + puts "ナップサック容量を超えない最大価値は #{res}" +end diff --git a/ja/codes/ruby/chapter_graph/graph_adjacency_list.rb b/ja/codes/ruby/chapter_graph/graph_adjacency_list.rb new file mode 100644 index 000000000..bf7213bde --- /dev/null +++ b/ja/codes/ruby/chapter_graph/graph_adjacency_list.rb @@ -0,0 +1,116 @@ +=begin +File: graph_adjacency_list.rb +Created Time: 2024-04-25 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/vertex' + +### 隣接リストで実装した無向グラフクラス ### +class GraphAdjList + attr_reader :adj_list + + ### コンストラクタ ### + def initialize(edges) + # 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 + @adj_list = {} + # すべての頂点と辺を追加 + for edge in edges + add_vertex(edge[0]) + add_vertex(edge[1]) + add_edge(edge[0], edge[1]) + end + end + + ### 頂点数を取得 ### + def size + @adj_list.length + end + + ### 辺を追加 ### + def add_edge(vet1, vet2) + raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2) + + @adj_list[vet1] << vet2 + @adj_list[vet2] << vet1 + end + + ### 辺を削除 ### + def remove_edge(vet1, vet2) + raise ArgumentError if !@adj_list.include?(vet1) || !@adj_list.include?(vet2) + + # 辺 vet1 - vet2 を削除 + @adj_list[vet1].delete(vet2) + @adj_list[vet2].delete(vet1) + end + + ### 頂点を追加 ### + def add_vertex(vet) + return if @adj_list.include?(vet) + + # 隣接リストに新しいリストを追加 + @adj_list[vet] = [] + end + + ### 頂点を削除 ### + def remove_vertex(vet) + raise ArgumentError unless @adj_list.include?(vet) + + # 隣接リストから頂点 vet に対応するリストを削除 + @adj_list.delete(vet) + # 他の頂点のリストを走査し、vet を含むすべての辺を削除 + for vertex in @adj_list + @adj_list[vertex.first].delete(vet) if @adj_list[vertex.first].include?(vet) + end + end + + ### 隣接リストを出力 ### + def __print__ + puts '隣接リスト =' + for vertex in @adj_list + tmp = @adj_list[vertex.first].map { |v| v.val } + puts "#{vertex.first.val}: #{tmp}," + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # 無向グラフを初期化 + v = vals_to_vets([1, 3, 2, 5, 4]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]], + ] + graph = GraphAdjList.new(edges) + puts "\n初期化後のグラフは" + graph.__print__ + + # 辺を追加する + # 頂点 1、2 は `v[0]`、`v[2]` + graph.add_edge(v[0], v[2]) + puts "\n辺 1-2 を追加した後のグラフは" + graph.__print__ + + # 辺を削除する + # 頂点 1, 3 はそれぞれ v[0], v[1] + graph.remove_edge(v[0], v[1]) + puts "\n辺 1-3 を削除した後のグラフは" + graph.__print__ + + # 頂点を追加 + v5 = Vertex.new(6) + graph.add_vertex(v5) + puts "\n頂点 6 を追加した後のグラフは" + graph.__print__ + + # 頂点を削除する + # 頂点 3 は v[1] + graph.remove_vertex(v[1]) + puts "\n頂点 3 を削除した後のグラフは" + graph.__print__ +end diff --git a/ja/codes/ruby/chapter_graph/graph_adjacency_matrix.rb b/ja/codes/ruby/chapter_graph/graph_adjacency_matrix.rb new file mode 100644 index 000000000..a1e7a6c54 --- /dev/null +++ b/ja/codes/ruby/chapter_graph/graph_adjacency_matrix.rb @@ -0,0 +1,116 @@ +=begin +File: graph_adjacency_matrix.rb +Created Time: 2024-04-25 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/print_util' + +### 隣接行列で実装した無向グラフクラス ### +class GraphAdjMat + def initialize(vertices, edges) + ### コンストラクタ ### + # 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す + @vertices = [] + # 隣接行列。行・列のインデックスは「頂点インデックス」に対応 + @adj_mat = [] + # 頂点を追加 + vertices.each { |val| add_vertex(val) } + # 辺を追加 + # 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する + edges.each { |e| add_edge(e[0], e[1]) } + end + + ### 頂点数を取得 ### + def size + @vertices.length + end + + ### 頂点を追加 ### + def add_vertex(val) + n = size + # 頂点リストに新しい頂点の値を追加 + @vertices << val + # 隣接行列に 1 行追加 + new_row = Array.new(n, 0) + @adj_mat << new_row + # 隣接行列に 1 列追加 + @adj_mat.each { |row| row << 0 } + end + + ### 頂点を削除 ### + def remove_vertex(index) + raise IndexError if index >= size + + # 頂点リストから index の頂点を削除する + @vertices.delete_at(index) + # 隣接行列で index 行を削除する + @adj_mat.delete_at(index) + # 隣接行列で index 列を削除する + @adj_mat.each { |row| row.delete_at(index) } + end + + ### 辺を追加 ### + def add_edge(i, j) + # パラメータ i, j は vertices の要素インデックスに対応する + # 範囲外と同値の場合の処理 + if i < 0 || j < 0 || i >= size || j >= size || i == j + raise IndexError + end + # 無向グラフでは、隣接行列は主対角線に関して対称、すなわち (i, j) == (j, i) を満たす + @adj_mat[i][j] = 1 + @adj_mat[j][i] = 1 + end + + ### 辺を削除 ### + def remove_edge(i, j) + # パラメータ i, j は vertices の要素インデックスに対応する + # 範囲外と同値の場合の処理 + if i < 0 || j < 0 || i >= size || j >= size || i == j + raise IndexError + end + @adj_mat[i][j] = 0 + @adj_mat[j][i] = 0 + end + + ### 隣接行列を出力 ### + def __print__ + puts "頂点リスト = #{@vertices}" + puts '隣接行列 =' + print_matrix(@adj_mat) + end +end + +### Driver Code ### +if __FILE__ == $0 + # 無向グラフを初期化する + # 注意: edges の要素は頂点インデックスであり、vertices の要素インデックスに対応する + vertices = [1, 3, 2, 5, 4] + edges = [[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]] + graph = GraphAdjMat.new(vertices, edges) + puts "\n初期化後のグラフは" + graph.__print__ + + # 辺を追加する + # 頂点 1, 2 のインデックスはそれぞれ 0, 2 + graph.add_edge(0, 2) + puts "\n辺 1-2 を追加した後のグラフは" + graph.__print__ + + # 辺を削除する + # 頂点 1, 3 のインデックスはそれぞれ 0, 1 + graph.remove_edge(0, 1) + puts "\n辺 1-3 を削除した後のグラフは" + graph.__print__ + + # 頂点を追加 + graph.add_vertex(6) + puts "\n頂点 6 を追加した後のグラフは" + graph.__print__ + + # 頂点を削除する + # 頂点 3 のインデックスは 1 + graph.remove_vertex(1) + puts "\n頂点 3 を削除した後のグラフは" + graph.__print__ +end diff --git a/ja/codes/ruby/chapter_graph/graph_bfs.rb b/ja/codes/ruby/chapter_graph/graph_bfs.rb new file mode 100644 index 000000000..c890b7f70 --- /dev/null +++ b/ja/codes/ruby/chapter_graph/graph_bfs.rb @@ -0,0 +1,61 @@ +=begin +File: graph_bfs.rb +Created Time: 2024-04-25 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require 'set' +require_relative './graph_adjacency_list' +require_relative '../utils/vertex' + +### 幅優先探索 ### +def graph_bfs(graph, start_vet) + # 指定した頂点の隣接頂点をすべて取得できるよう、隣接リストでグラフを表現する + # 頂点の走査順序 + res = [] + # 訪問済み頂点を記録するためのハッシュ集合 + visited = Set.new([start_vet]) + # BFS の実装にキューを用いる + que = [start_vet] + # 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す + while que.length > 0 + vet = que.shift # 先頭の頂点をデキュー + res << vet # 訪問した頂点を記録 + # この頂点のすべての隣接頂点を走査 + for adj_vet in graph.adj_list[vet] + next if visited.include?(adj_vet) # 訪問済みの頂点をスキップ + que << adj_vet # 未訪問の頂点のみをキューに追加 + visited.add(adj_vet) # この頂点を訪問済みにする + end + end + # 頂点の走査順を返す + res +end + +### Driver Code ### +if __FILE__ == $0 + # 無向グラフを初期化 + v = vals_to_vets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[1], v[4]], + [v[2], v[5]], + [v[3], v[4]], + [v[3], v[6]], + [v[4], v[5]], + [v[4], v[7]], + [v[5], v[8]], + [v[6], v[7]], + [v[7], v[8]], + ] + graph = GraphAdjList.new(edges) + puts "\n初期化後のグラフは" + graph.__print__ + + # 幅優先探索 + res = graph_bfs(graph, v.first) + puts "\n幅優先探索(BFS)の頂点順序は" + p vets_to_vals(res) +end diff --git a/ja/codes/ruby/chapter_graph/graph_dfs.rb b/ja/codes/ruby/chapter_graph/graph_dfs.rb new file mode 100644 index 000000000..77c124a65 --- /dev/null +++ b/ja/codes/ruby/chapter_graph/graph_dfs.rb @@ -0,0 +1,54 @@ +=begin +File: graph_dfs.rb +Created Time: 2024-04-25 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require 'set' +require_relative './graph_adjacency_list' +require_relative '../utils/vertex' + +### 深さ優先探索の補助関数 ### +def dfs(graph, visited, res, vet) + res << vet # 訪問した頂点を記録 + visited.add(vet) # この頂点を訪問済みにする + # この頂点のすべての隣接頂点を走査 + for adj_vet in graph.adj_list[vet] + next if visited.include?(adj_vet) # 訪問済みの頂点をスキップ + # 隣接頂点を再帰的に訪問 + dfs(graph, visited, res, adj_vet) + end +end + +### 深さ優先探索 ### +def graph_dfs(graph, start_vet) + # 指定した頂点の隣接頂点をすべて取得できるよう、隣接リストでグラフを表現する + # 頂点の走査順序 + res = [] + # 訪問済み頂点を記録するためのハッシュ集合 + visited = Set.new + dfs(graph, visited, res, start_vet) + res +end + +### Driver Code ### +if __FILE__ == $0 + # 無向グラフを初期化 + v = vals_to_vets([0, 1, 2, 3, 4, 5, 6]) + edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], + ] + graph = GraphAdjList.new(edges) + puts "\n初期化後のグラフは" + graph.__print__ + + # 深さ優先探索 + res = graph_dfs(graph, v[0]) + puts "\n深さ優先探索(DFS)の頂点順序は" + p vets_to_vals(res) +end diff --git a/ja/codes/ruby/chapter_greedy/coin_change_greedy.rb b/ja/codes/ruby/chapter_greedy/coin_change_greedy.rb new file mode 100644 index 000000000..66871ff25 --- /dev/null +++ b/ja/codes/ruby/chapter_greedy/coin_change_greedy.rb @@ -0,0 +1,50 @@ +=begin +File: coin_change_greedy.rb +Created Time: 2024-05-07 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### コイン両替:貪欲法 ### +def coin_change_greedy(coins, amt) + # coins リストはソート済みと仮定する + i = coins.length - 1 + count = 0 + # 残額がなくなるまで貪欲選択を繰り返す + while amt > 0 + # 残額以下で最も近い硬貨を見つける + while i > 0 && coins[i] > amt + i -= 1 + end + # coins[i] を選択する + amt -= coins[i] + count += 1 + end + # 実行可能な解が見つからなければ `-1` を返す + amt == 0 ? count : -1 +end + +### Driver Code ### +if __FILE__ == $0 + # 貪欲法:大域最適解を保証できる + coins = [1, 5, 10, 20, 50, 100] + amt = 186 + res = coin_change_greedy(coins, amt) + puts "\ncoins = #{coins}, amt = #{amt}" + puts "#{amt} にするために必要な最小硬貨枚数は #{res}" + + # 貪欲法:大域最適解を保証できない + coins = [1, 20, 50] + amt = 60 + res = coin_change_greedy(coins, amt) + puts "\ncoins = #{coins}, amt = #{amt}" + puts "#{amt} にするために必要な最小硬貨枚数は #{res}" + puts "実際に必要な最小個数は 3 、つまり 20 + 20 + 20" + + # 貪欲法:大域最適解を保証できない + coins = [1, 49, 50] + amt = 98 + res = coin_change_greedy(coins, amt) + puts "\ncoins = #{coins}, amt = #{amt}" + puts "#{amt} にするために必要な最小硬貨枚数は #{res}" + puts "実際に必要な最小個数は 2 、つまり 49 + 49" +end diff --git a/ja/codes/ruby/chapter_greedy/fractional_knapsack.rb b/ja/codes/ruby/chapter_greedy/fractional_knapsack.rb new file mode 100644 index 000000000..3303504f7 --- /dev/null +++ b/ja/codes/ruby/chapter_greedy/fractional_knapsack.rb @@ -0,0 +1,51 @@ +=begin +File: fractional_knapsack.rb +Created Time: 2024-05-07 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### アイテム ### +class Item + attr_accessor :w # 品物の重さ + attr_accessor :v # 品物の価値 + + def initialize(w, v) + @w = w + @v = v + end +end + +### 分数ナップサック:貪欲法 ### +def fractional_knapsack(wgt, val, cap) + # 重さと価値の 2 属性を持つ品物リストを作成する + items = wgt.each_with_index.map { |w, i| Item.new(w, val[i]) } + # 単位価値 item.v / item.w の高い順にソートする + items.sort! { |a, b| (b.v.to_f / b.w) <=> (a.v.to_f / a.w) } + # 貪欲選択を繰り返す + res = 0 + for item in items + if item.w <= cap + # 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる + res += item.v + cap -= item.w + else + # 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる + res += (item.v.to_f / item.w) * cap + # 残り容量がないため、ループを抜ける + break + end + end + res +end + +### Driver Code ### +if __FILE__ == $0 + wgt = [10, 20, 30, 40, 50] + val = [50, 120, 150, 210, 240] + cap = 50 + n = wgt.length + + # 貪欲法 + res = fractional_knapsack(wgt, val, cap) + puts "ナップサック容量を超えない最大価値は #{res}" +end diff --git a/ja/codes/ruby/chapter_greedy/max_capacity.rb b/ja/codes/ruby/chapter_greedy/max_capacity.rb new file mode 100644 index 000000000..5ce224960 --- /dev/null +++ b/ja/codes/ruby/chapter_greedy/max_capacity.rb @@ -0,0 +1,37 @@ +=begin +File: max_capacity.rb +Created Time: 2024-05-07 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 最大容量:貪欲法 ### +def max_capacity(ht) + # i, j を初期化し、それぞれ配列の両端に置く + i, j = 0, ht.length - 1 + # 初期の最大容量は 0 + res = 0 + + # 2 枚の板が出会うまで貪欲選択を繰り返す + while i < j + # 最大容量を更新する + cap = [ht[i], ht[j]].min * (j - i) + res = [res, cap].max + # 短い方を内側へ動かす + if ht[i] < ht[j] + i += 1 + else + j -= 1 + end + end + + res +end + +### Driver Code ### +if __FILE__ == $0 + ht = [3, 8, 5, 2, 7, 7, 3, 4] + + # 貪欲法 + res = max_capacity(ht) + puts "最大容量は #{res}" +end diff --git a/ja/codes/ruby/chapter_greedy/max_product_cutting.rb b/ja/codes/ruby/chapter_greedy/max_product_cutting.rb new file mode 100644 index 000000000..f1bef405d --- /dev/null +++ b/ja/codes/ruby/chapter_greedy/max_product_cutting.rb @@ -0,0 +1,28 @@ +=begin +File: max_product_cutting.rb +Created Time: 2024-05-07 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 最大分割積:貪欲法 ### +def max_product_cutting(n) + # n <= 3 のときは、必ず 1 を切り出す + return 1 * (n - 1) if n <= 3 + # 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする + a, b = n / 3, n % 3 + # 余りが 1 のときは、1 * 3 を 2 * 2 に変える + return (3.pow(a - 1) * 2 * 2).to_i if b == 1 + # 余りが 2 のときは、そのままにする + return (3.pow(a) * 2).to_i if b == 2 + # 余りが 0 のときは、そのままにする + 3.pow(a).to_i +end + +### Driver Code ### +if __FILE__ == $0 + n = 58 + + # 貪欲法 + res = max_product_cutting(n) + puts "最大分割積は #{res}" +end diff --git a/ja/codes/ruby/chapter_hashing/array_hash_map.rb b/ja/codes/ruby/chapter_hashing/array_hash_map.rb new file mode 100644 index 000000000..9cb8e9079 --- /dev/null +++ b/ja/codes/ruby/chapter_hashing/array_hash_map.rb @@ -0,0 +1,121 @@ +=begin +File: array_hash_map.rb +Created Time: 2024-04-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### キーと値のペア ### +class Pair + attr_accessor :key, :val + + def initialize(key, val) + @key = key + @val = val + end +end + +### 配列で実装したハッシュテーブル ### +class ArrayHashMap + ### コンストラクタ ### + def initialize + # 100 個のバケットを含む配列を初期化 + @buckets = Array.new(100) + end + + ### ハッシュ関数 ### + def hash_func(key) + index = key % 100 + end + + ### 検索操作 ### + def get(key) + index = hash_func(key) + pair = @buckets[index] + + return if pair.nil? + pair.val + end + + ### 追加操作 ### + def put(key, val) + pair = Pair.new(key, val) + index = hash_func(key) + @buckets[index] = pair + end + + ### 削除操作 ### + def remove(key) + index = hash_func(key) + # nil に設定し、削除を表す + @buckets[index] = nil + end + + ### すべてのキーと値のペアを取得 ### + def entry_set + result = [] + @buckets.each { |pair| result << pair unless pair.nil? } + result + end + + ### すべてのキーを取得 ### + def key_set + result = [] + @buckets.each { |pair| result << pair.key unless pair.nil? } + result + end + + ### すべての値を取得 ### + def value_set + result = [] + @buckets.each { |pair| result << pair.val unless pair.nil? } + result + end + + ### ハッシュテーブルを出力 ### + def print + @buckets.each { |pair| puts "#{pair.key} -> #{pair.val}" unless pair.nil? } + end +end + +### Driver Code ### +if __FILE__ == $0 + # ハッシュテーブルを初期化 + hmap = ArrayHashMap.new + + # 追加操作 + # ハッシュテーブルにキーと値の組 (key, value) を追加する + hmap.put(12836, "シャオハー") + hmap.put(15937, "シャオルオ") + hmap.put(16750, "シャオスワン") + hmap.put(13276, "シャオファー") + hmap.put(10583, "シャオヤー") + puts "\n追加完了後、ハッシュテーブルは\nKey -> Value" + hmap.print + + # 検索操作 + # ハッシュテーブルにキー `key` を入力し、値 `value` を取得する + name = hmap.get(15937) + puts "\n学籍番号 15937 を入力すると、名前 #{name} が見つかりました" + + # 削除操作 + # ハッシュテーブルからキーと値の組 (key, value) を削除する + hmap.remove(10583) + puts "\n10583 を削除した後、ハッシュテーブルは\nKey -> Value" + hmap.print + + # ハッシュテーブルを走査 + puts "\nキーと値のペア Key->Value を走査" + for pair in hmap.entry_set + puts "#{pair.key} -> #{pair.val}" + end + + puts "\nキー Key のみを個別に走査" + for key in hmap.key_set + puts key + end + + puts "\n値 Value のみを個別に走査" + for val in hmap.value_set + puts val + end +end diff --git a/ja/codes/ruby/chapter_hashing/built_in_hash.rb b/ja/codes/ruby/chapter_hashing/built_in_hash.rb new file mode 100644 index 000000000..2bc07190a --- /dev/null +++ b/ja/codes/ruby/chapter_hashing/built_in_hash.rb @@ -0,0 +1,34 @@ +=begin +File: built_in_hash.rb +Created Time: 2024-04-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' + +### Driver Code ### +if __FILE__ == $0 + num = 3 + hash_num = num.hash + puts "整数 #{num} のハッシュ値は #{hash_num}" + + bol = true + hash_bol = bol.hash + puts "ブール値 #{bol} のハッシュ値は #{hash_bol}" + + dec = 3.14159 + hash_dec = dec.hash + puts "小数 #{dec} のハッシュ値は #{hash_dec}" + + str = "Hello アルゴリズム" + hash_str = str.hash + puts "文字列 #{str} のハッシュ値は #{hash_str}" + + tup = [12836, 'シャオハー'] + hash_tup = tup.hash + puts "タプル #{tup} のハッシュ値は #{hash_tup}" + + obj = ListNode.new(0) + hash_obj = obj.hash + puts "ノードオブジェクト #{obj} のハッシュ値は #{hash_obj}" +end diff --git a/ja/codes/ruby/chapter_hashing/hash_map.rb b/ja/codes/ruby/chapter_hashing/hash_map.rb new file mode 100644 index 000000000..9a2c8b93f --- /dev/null +++ b/ja/codes/ruby/chapter_hashing/hash_map.rb @@ -0,0 +1,44 @@ +=begin +File: hash_map.rb +Created Time: 2024-04-14 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/print_util' + +### Driver Code ### +if __FILE__ == $0 + # ハッシュテーブルを初期化 + hmap = {} + + # 追加操作 + # ハッシュテーブルにキーと値の組 (key, value) を追加する + hmap[12836] = "シャオハー" + hmap[15937] = "シャオルオ" + hmap[16750] = "シャオスワン" + hmap[13276] = "シャオファー" + hmap[10583] = "シャオヤー" + puts "\n追加完了後、ハッシュテーブルは\nKey -> Value" + print_hash_map(hmap) + + # 検索操作 + # ハッシュテーブルにキー key を入力し、値 value を取得する + name = hmap[15937] + puts "\n学籍番号 15937 を入力すると、名前 #{name} が見つかりました" + + # 削除操作 + # ハッシュテーブルからキーと値の組 (key, value) を削除する + hmap.delete(10583) + puts "\n10583 を削除した後、ハッシュテーブルは\nKey -> Value" + print_hash_map(hmap) + + # ハッシュテーブルを走査 + puts "\nキーと値のペア Key->Value を走査" + hmap.entries.each { |key, value| puts "#{key} -> #{value}" } + + puts "\nキー Key のみを個別に走査" + hmap.keys.each { |key| puts key } + + puts "\n値 Value のみを個別に走査" + hmap.values.each { |val| puts val } +end diff --git a/ja/codes/ruby/chapter_hashing/hash_map_chaining.rb b/ja/codes/ruby/chapter_hashing/hash_map_chaining.rb new file mode 100644 index 000000000..430800de9 --- /dev/null +++ b/ja/codes/ruby/chapter_hashing/hash_map_chaining.rb @@ -0,0 +1,128 @@ +=begin +File: hash_map_chaining.rb +Created Time: 2024-04-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative './array_hash_map' + +### キーアドレス法ハッシュテーブル ### +class HashMapChaining + ### コンストラクタ ### + def initialize + @size = 0 # キーと値のペア数 + @capacity = 4 # ハッシュテーブル容量 + @load_thres = 2.0 / 3.0 # リサイズを発動する負荷率のしきい値 + @extend_ratio = 2 # 拡張倍率 + @buckets = Array.new(@capacity) { [] } # バケット配列 + end + + ### ハッシュ関数 ### + def hash_func(key) + key % @capacity + end + + ### 負荷率 ### + def load_factor + @size / @capacity + end + + ### 検索操作 ### + def get(key) + index = hash_func(key) + bucket = @buckets[index] + # バケットを走査し、key が見つかれば対応する val を返す + for pair in bucket + return pair.val if pair.key == key + end + # `key` が見つからなければ `nil` を返す + nil + end + + ### 追加操作 ### + def put(key, val) + # 負荷率がしきい値を超えたら、リサイズを実行 + extend if load_factor > @load_thres + index = hash_func(key) + bucket = @buckets[index] + # バケットを走査し、指定した key が見つかれば対応する val を更新して返す + for pair in bucket + if pair.key == key + pair.val = val + return + end + end + # その key が存在しなければ、キーと値のペアを末尾に追加 + pair = Pair.new(key, val) + bucket << pair + @size += 1 + end + + ### 削除操作 ### + def remove(key) + index = hash_func(key) + bucket = @buckets[index] + # バケットを走査してキーと値のペアを削除 + for pair in bucket + if pair.key == key + bucket.delete(pair) + @size -= 1 + break + end + end + end + + ### ハッシュテーブルを拡張 ### + def extend + # 元のハッシュテーブルを一時保存 + buckets = @buckets + # リサイズ後の新しいハッシュテーブルを初期化 + @capacity *= @extend_ratio + @buckets = Array.new(@capacity) { [] } + @size = 0 + # キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す + for bucket in buckets + for pair in bucket + put(pair.key, pair.val) + end + end + end + + ### ハッシュテーブルを出力 ### + def print + for bucket in @buckets + res = [] + for pair in bucket + res << "#{pair.key} -> #{pair.val}" + end + pp res + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # ## ハッシュテーブルを初期化 + hashmap = HashMapChaining.new + + # 追加操作 + # ハッシュテーブルにキーと値の組 (key, value) を追加する + hashmap.put(12836, "シャオハー") + hashmap.put(15937, "シャオルオ") + hashmap.put(16750, "シャオスワン") + hashmap.put(13276, "シャオファー") + hashmap.put(10583, "シャオヤー") + puts "\n追加完了後、ハッシュテーブルは\n[Key1 -> Value1, Key2 -> Value2, ...]" + hashmap.print + + # 検索操作 + # ハッシュテーブルにキー key を入力し、値 value を取得する + name = hashmap.get(13276) + puts "\n学籍番号 13276 を入力すると、名前 #{name} が見つかりました" + + # 削除操作 + # ハッシュテーブルからキーと値の組 (key, value) を削除する + hashmap.remove(12836) + puts "\n12836 を削除した後、ハッシュテーブルは\n[Key1 -> Value1, Key2 -> Value2, ...]" + hashmap.print +end diff --git a/ja/codes/ruby/chapter_hashing/hash_map_open_addressing.rb b/ja/codes/ruby/chapter_hashing/hash_map_open_addressing.rb new file mode 100644 index 000000000..5f75939c8 --- /dev/null +++ b/ja/codes/ruby/chapter_hashing/hash_map_open_addressing.rb @@ -0,0 +1,147 @@ +=begin +File: hash_map_open_addressing.rb +Created Time: 2024-04-13 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative './array_hash_map' + +### オープンアドレス法ハッシュテーブル ### +class HashMapOpenAddressing + TOMBSTONE = Pair.new(-1, '-1') # 削除済みマーク + + ### コンストラクタ ### + def initialize + @size = 0 # キーと値のペア数 + @capacity = 4 # ハッシュテーブル容量 + @load_thres = 2.0 / 3.0 # リサイズを発動する負荷率のしきい値 + @extend_ratio = 2 # 拡張倍率 + @buckets = Array.new(@capacity) # バケット配列 + end + + ### ハッシュ関数 ### + def hash_func(key) + key % @capacity + end + + ### 負荷率 ### + def load_factor + @size / @capacity + end + + ### key に対応するバケットインデックスを検索 ### + def find_bucket(key) + index = hash_func(key) + first_tombstone = -1 + # 線形プロービングを行い、空バケットに達したら終了 + while !@buckets[index].nil? + # key が見つかったら、対応するバケットのインデックスを返す + if @buckets[index].key == key + # 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 + if first_tombstone != -1 + @buckets[first_tombstone] = @buckets[index] + @buckets[index] = TOMBSTONE + return first_tombstone # 移動後のバケットインデックスを返す + end + return index # バケットのインデックスを返す + end + # 最初に見つかった削除マークを記録 + first_tombstone = index if first_tombstone == -1 && @buckets[index] == TOMBSTONE + # バケットのインデックスを計算し、末尾を越えたら先頭に戻る + index = (index + 1) % @capacity + end + # key が存在しない場合は追加位置のインデックスを返す + first_tombstone == -1 ? index : first_tombstone + end + + ### 検索操作 ### + def get(key) + # key に対応するバケットインデックスを探す + index = find_bucket(key) + # キーと値の組が見つかったら、対応する val を返す + return @buckets[index].val unless [nil, TOMBSTONE].include?(@buckets[index]) + # キーと値のペアが存在しない場合は `nil` を返す + nil + end + + ### 追加操作 ### + def put(key, val) + # 負荷率がしきい値を超えたら、リサイズを実行 + extend if load_factor > @load_thres + # key に対応するバケットインデックスを探す + index = find_bucket(key) + # キーと値のペアが見つかった場合は、`val` を上書きして返す + unless [nil, TOMBSTONE].include?(@buckets[index]) + @buckets[index].val = val + return + end + # キーと値の組が存在しない場合は、その組を追加する + @buckets[index] = Pair.new(key, val) + @size += 1 + end + + ### 削除操作 ### + def remove(key) + # key に対応するバケットインデックスを探す + index = find_bucket(key) + # キーと値の組が見つかったら、削除マーカーで上書きする + unless [nil, TOMBSTONE].include?(@buckets[index]) + @buckets[index] = TOMBSTONE + @size -= 1 + end + end + + ### ハッシュテーブルを拡張 ### + def extend + # 元のハッシュテーブルを一時保存 + buckets_tmp = @buckets + # リサイズ後の新しいハッシュテーブルを初期化 + @capacity *= @extend_ratio + @buckets = Array.new(@capacity) + @size = 0 + # キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す + for pair in buckets_tmp + put(pair.key, pair.val) unless [nil, TOMBSTONE].include?(pair) + end + end + + ### ハッシュテーブルを出力 ### + def print + for pair in @buckets + if pair.nil? + puts "Nil" + elsif pair == TOMBSTONE + puts "TOMBSTONE" + else + puts "#{pair.key} -> #{pair.val}" + end + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # ハッシュテーブルを初期化 + hashmap = HashMapOpenAddressing.new + + # 追加操作 + # ハッシュテーブルにキーと値の組 (key, val) を追加する + hashmap.put(12836, "シャオハー") + hashmap.put(15937, "シャオルオ") + hashmap.put(16750, "シャオスワン") + hashmap.put(13276, "シャオファー") + hashmap.put(10583, "シャオヤー") + puts "\n追加完了後、ハッシュテーブルは\nKey -> Value" + hashmap.print + + # 検索操作 + # ハッシュテーブルにキー key を入力し、値 val を得る + name = hashmap.get(13276) + puts "\n学籍番号 13276 を入力すると、名前 #{name} が見つかりました" + + # 削除操作 + # ハッシュテーブルからキーと値の組 (key, val) を削除する + hashmap.remove(16750) + puts "\n16750 を削除した後、ハッシュテーブルは\nKey -> Value" + hashmap.print +end diff --git a/ja/codes/ruby/chapter_hashing/simple_hash.rb b/ja/codes/ruby/chapter_hashing/simple_hash.rb new file mode 100644 index 000000000..598ef7abf --- /dev/null +++ b/ja/codes/ruby/chapter_hashing/simple_hash.rb @@ -0,0 +1,62 @@ +=begin +File: simple_hash.rb +Created Time: 2024-04-14 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 加算ハッシュ ### +def add_hash(key) + hash = 0 + modulus = 1_000_000_007 + + key.each_char { |c| hash += c.ord } + + hash % modulus +end + +### 乗算ハッシュ ### +def mul_hash(key) + hash = 0 + modulus = 1_000_000_007 + + key.each_char { |c| hash = 31 * hash + c.ord } + + hash % modulus +end + +### XOR ハッシュ ### +def xor_hash(key) + hash = 0 + modulus = 1_000_000_007 + + key.each_char { |c| hash ^= c.ord } + + hash % modulus +end + +### 回転ハッシュ ### +def rot_hash(key) + hash = 0 + modulus = 1_000_000_007 + + key.each_char { |c| hash = (hash << 4) ^ (hash >> 28) ^ c.ord } + + hash % modulus +end + +### Driver Code ### +if __FILE__ == $0 + key = "Hello アルゴリズム" + + hash = add_hash(key) + puts "加算ハッシュ値は #{hash}" + + hash = mul_hash(key) + puts "乗算ハッシュ値は #{hash}" + + hash = xor_hash(key) + puts "XORハッシュ値は #{hash}" + + hash = rot_hash(key) + puts "ローテーションハッシュ値は #{hash}" +end diff --git a/ja/codes/ruby/chapter_heap/my_heap.rb b/ja/codes/ruby/chapter_heap/my_heap.rb new file mode 100644 index 000000000..7d4e9dfe6 --- /dev/null +++ b/ja/codes/ruby/chapter_heap/my_heap.rb @@ -0,0 +1,147 @@ +=begin +File: my_heap.rb +Created Time: 2024-04-19 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +require_relative '../utils/print_util' + +### 最大ヒープ ### +class MaxHeap + attr_reader :max_heap + + ### コンストラクタ。入力リストに基づいてヒープを構築 ### + def initialize(nums) + # リスト要素をそのままヒープに追加 + @max_heap = nums + # 葉ノード以外のすべてのノードをヒープ化 + parent(size - 1).downto(0) do |i| + sift_down(i) + end + end + + ### 左子ノードのインデックスを取得 ### + def left(i) + 2 * i + 1 + end + + ### 右子ノードのインデックスを取得 ### + def right(i) + 2 * i + 2 + end + + ### 親ノードのインデックスを取得 ### + def parent(i) + (i - 1) / 2 # 切り捨て除算 + end + + ### 要素を交換 ### + def swap(i, j) + @max_heap[i], @max_heap[j] = @max_heap[j], @max_heap[i] + end + + ### ヒープのサイズを取得 ### + def size + @max_heap.length + end + + ### ヒープが空かどうかを判定 ### + def is_empty? + size == 0 + end + + ### ヒープ先頭要素を参照 ### + def peek + @max_heap[0] + end + + ### 要素をヒープに挿入 ### + def push(val) + # ノードを追加 + @max_heap << val + # 下から上へヒープ化 + sift_up(size - 1) + end + + ### ノード i から下から上へヒープ化 ### + def sift_up(i) + loop do + # ノード i の親ノードを取得 + p = parent(i) + # 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 + break if p < 0 || @max_heap[i] <= @max_heap[p] + # 2 つのノードを交換 + swap(i, p) + # ループで下から上へヒープ化 + i = p + end + end + + ### 要素をヒープから取り出す ### + def pop + # 空判定の処理 + raise IndexError, "ヒープが空です" if is_empty? + # 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + swap(0, size - 1) + # ノードを削除 + val = @max_heap.pop + # 上から下へヒープ化 + sift_down(0) + # ヒープ先頭要素を返す + val + end + + ### ノード i から上から下へヒープ化 ### + def sift_down(i) + loop do + # ノード i, l, r のうち値が最大のノードを ma とする + l, r, ma = left(i), right(i), i + ma = l if l < size && @max_heap[l] > @max_heap[ma] + ma = r if r < size && @max_heap[r] > @max_heap[ma] + + # ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + break if ma == i + + # 2 つのノードを交換 + swap(i, ma) + # ループで上から下へヒープ化 + i = ma + end + end + + # ## ヒープを表示(二分木)### + def __print__ + print_heap(@max_heap) + end +end + +### Driver Code ### +if __FILE__ == $0 + # 最大ヒープを初期化 + max_heap = MaxHeap.new([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) + puts "\nリストを入力してヒープを構築した後" + max_heap.__print__ + + # ヒープ頂点の要素を取得 + peek = max_heap.peek + puts "\nヒープの先頭要素は #{peek}" + + # 要素をヒープに追加 + val = 7 + max_heap.push(val) + puts "\n要素 #{val} をヒープに追加した後" + max_heap.__print__ + + # ヒープ頂点の要素を取り出す + peek = max_heap.pop + puts "\nヒープの先頭要素 #{peek} を取り出した後" + max_heap.__print__ + + # ヒープのサイズを取得 + size = max_heap.size + puts "\nヒープ要素数は #{size}" + + # ヒープが空かどうかを判定 + is_empty = max_heap.is_empty? + puts "\nヒープが空かどうかは #{is_empty}" +end diff --git a/ja/codes/ruby/chapter_heap/top_k.rb b/ja/codes/ruby/chapter_heap/top_k.rb new file mode 100644 index 000000000..5d68db99f --- /dev/null +++ b/ja/codes/ruby/chapter_heap/top_k.rb @@ -0,0 +1,64 @@ +=begin +File: top_k.rb +Created Time: 2024-04-19 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +require_relative "./my_heap" + +### 要素をヒープに挿入 ### +def push_min_heap(heap, val) + # 要素を反転する + heap.push(-val) +end + +### 要素をヒープから取り出す ### +def pop_min_heap(heap) + # 要素を反転する + -heap.pop +end + +### ヒープ先頭要素を参照 ### +def peek_min_heap(heap) + # 要素を反転する + -heap.peek +end + +### ヒープから要素を取り出す ### +def get_min_heap(heap) + # ヒープ内のすべての要素を反転 + heap.max_heap.map { |x| -x } +end + +### ヒープに基づいて配列中の最大 k 個の要素を探す ### +def top_k_heap(nums, k) + # 最小ヒープを初期化する + # 注意: ヒープ内の全要素を反転し、最大ヒープで最小ヒープをシミュレートする + max_heap = MaxHeap.new([]) + + # 配列の先頭 k 個の要素をヒープに追加 + for i in 0...k + push_min_heap(max_heap, nums[i]) + end + + # k+1 番目の要素から開始し、ヒープ長を k に保つ + for i in k...nums.length + # 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する + if nums[i] > peek_min_heap(max_heap) + pop_min_heap(max_heap) + push_min_heap(max_heap, nums[i]) + end + end + + get_min_heap(max_heap) +end + +### Driver Code ### +if __FILE__ == $0 + nums = [1, 7, 6, 3, 2] + k = 3 + + res = top_k_heap(nums, k) + puts "最大の #{k} 個の要素は" + print_heap(res) +end diff --git a/ja/codes/ruby/chapter_searching/binary_search.rb b/ja/codes/ruby/chapter_searching/binary_search.rb new file mode 100644 index 000000000..8c6680ff2 --- /dev/null +++ b/ja/codes/ruby/chapter_searching/binary_search.rb @@ -0,0 +1,63 @@ +=begin +File: binary_search.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +### 二分探索(両閉区間) ### +def binary_search(nums, target) + # 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す + i, j = 0, nums.length - 1 + + # ループし、探索区間が空になったら終了する(i > j で空) + while i <= j + # 理論上、Ruby の数値は無限に大きくできるため(メモリ容量に依存)、大きな数のオーバーフローを考慮する必要はない + m = (i + j) / 2 # 中点インデックス m を計算 + + if nums[m] < target + i = m + 1 # この場合、target は区間 [m+1, j] にある + elsif nums[m] > target + j = m - 1 # この場合、target は区間 [i, m-1] にある + else + return m # 目標要素が見つかったらそのインデックスを返す + end + end + + -1 # 目標要素が見つからなければ -1 を返す +end + +### 二分探索(左閉右開区間) ### +def binary_search_lcro(nums, target) + # 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す + i, j = 0, nums.length + + # ループし、探索区間が空になったら終了する(i = j で空) + while i < j + # 中点インデックス m を計算 + m = (i + j) / 2 + + if nums[m] < target + i = m + 1 # この場合、target は区間 [m+1, j) にある + elsif nums[m] > target + j = m - 1 # この場合、target は区間 [i, m) にある + else + return m # 目標要素が見つかったらそのインデックスを返す + end + end + + -1 # 目標要素が見つからなければ -1 を返す +end + +### Driver Code ### +if __FILE__ == $0 + target = 6 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + # 二分探索(両閉区間) + index = binary_search(nums, target) + puts "対象要素 6 のインデックス = #{index}" + + # 二分探索(左閉右開区間) + index = binary_search_lcro(nums, target) + puts "対象要素 6 のインデックス = #{index}" +end diff --git a/ja/codes/ruby/chapter_searching/binary_search_edge.rb b/ja/codes/ruby/chapter_searching/binary_search_edge.rb new file mode 100644 index 000000000..7ede78e7b --- /dev/null +++ b/ja/codes/ruby/chapter_searching/binary_search_edge.rb @@ -0,0 +1,47 @@ +=begin +File: binary_search_edge.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +require_relative './binary_search_insertion' + +### target の最左位置を二分探索 ### +def binary_search_left_edge(nums, target) + # target の挿入位置を探すのと等価 + i = binary_search_insertion(nums, target) + + # target が見つからなければ、-1 を返す + return -1 if i == nums.length || nums[i] != target + + i # target が見つかったら、インデックス i を返す +end + +### target の最右位置を二分探索 ### +def binary_search_right_edge(nums, target) + # 最左の target + 1 を探す問題に変換する + i = binary_search_insertion(nums, target + 1) + + # j は最も右の target を指し、i は target より大きい最初の要素を指す + j = i - 1 + + # target が見つからなければ、-1 を返す + return -1 if j == -1 || nums[j] != target + + j # target が見つかったら、インデックス j を返す +end + +### Driver Code ### +if __FILE__ == $0 + # 重複要素を含む配列 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + puts "\n配列 nums = #{nums}" + + # 二分探索で左端と右端を探す + for target in [6, 7] + index = binary_search_left_edge(nums, target) + puts "最も左の要素 #{target} のインデックスは #{index}" + index = binary_search_right_edge(nums, target) + puts "最も右の要素 #{target} のインデックスは #{index}" + end +end diff --git a/ja/codes/ruby/chapter_searching/binary_search_insertion.rb b/ja/codes/ruby/chapter_searching/binary_search_insertion.rb new file mode 100644 index 000000000..b68b39752 --- /dev/null +++ b/ja/codes/ruby/chapter_searching/binary_search_insertion.rb @@ -0,0 +1,68 @@ +=begin +File: binary_search_insertion.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +### 二分探索の挿入位置(重複要素なし) ### +def binary_search_insertion_simple(nums, target) + # 両閉区間 [0, n-1] を初期化 + i, j = 0, nums.length - 1 + + while i <= j + # 中点インデックス m を計算 + m = (i + j) / 2 + + if nums[m] < target + i = m + 1 # target は区間 [m+1, j] にある + elsif nums[m] > target + j = m - 1 # target は区間 [i, m-1] にある + else + return m # target が見つかったら、挿入位置 m を返す + end + end + + i # target が見つからなければ、挿入位置 i を返す +end + +### 二分探索の挿入位置(重複要素あり) ### +def binary_search_insertion(nums, target) + # 両閉区間 [0, n-1] を初期化 + i, j = 0, nums.length - 1 + + while i <= j + # 中点インデックス m を計算 + m = (i + j) / 2 + + if nums[m] < target + i = m + 1 # target は区間 [m+1, j] にある + elsif nums[m] > target + j = m - 1 # target は区間 [i, m-1] にある + else + j = m - 1 # target より小さい最初の要素は区間 [i, m-1] にある + end + end + + i # 挿入位置 i を返す +end + +### Driver Code ### +if __FILE__ == $0 + # 重複要素のない配列 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + puts "\n配列 nums = #{nums}" + # 二分探索で挿入位置を探す + for target in [6, 9] + index = binary_search_insertion_simple(nums, target) + puts "要素 #{target} の挿入位置のインデックスは #{index}" + end + + # 重複要素を含む配列 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + puts "\n配列 nums = #{nums}" + # 二分探索で挿入位置を探す + for target in [2, 6, 20] + index = binary_search_insertion(nums, target) + puts "要素 #{target} の挿入位置のインデックスは #{index}" + end +end diff --git a/ja/codes/ruby/chapter_searching/hashing_search.rb b/ja/codes/ruby/chapter_searching/hashing_search.rb new file mode 100644 index 000000000..2402ef3bc --- /dev/null +++ b/ja/codes/ruby/chapter_searching/hashing_search.rb @@ -0,0 +1,47 @@ +=begin +File: hashing_search.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +require_relative '../utils/list_node' + +### ハッシュ検索(配列) ### +def hashing_search_array(hmap, target) + # ハッシュテーブルの key: 目標要素、value: インデックス + # ハッシュテーブルにこの key がなければ -1 を返す + hmap[target] || -1 +end + +### ハッシュ検索(連結リスト) ### +def hashing_search_linkedlist(hmap, target) + # ハッシュテーブルの key: 対象要素、value: ノードオブジェクト + # ハッシュテーブルにこの key がなければ None を返す + hmap[target] || nil +end + +### Driver Code ### +if __FILE__ == $0 + target = 3 + + # ハッシュ探索(配列) + nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + # ハッシュテーブルを初期化 + map0 = {} + for i in 0...nums.length + map0[nums[i]] = i # key: 要素、value: インデックス + end + index = hashing_search_array(map0, target) + puts "対象要素 3 のインデックス = #{index}" + + # ハッシュ探索(連結リスト) + head = arr_to_linked_list(nums) + # ハッシュテーブルを初期化 + map1 = {} + while head + map1[head.val] = head + head = head.next + end + node = hashing_search_linkedlist(map1, target) + puts "対象ノード値 3 に対応するノードオブジェクトは #{node}" +end diff --git a/ja/codes/ruby/chapter_searching/linear_search.rb b/ja/codes/ruby/chapter_searching/linear_search.rb new file mode 100644 index 000000000..71ff102bf --- /dev/null +++ b/ja/codes/ruby/chapter_searching/linear_search.rb @@ -0,0 +1,44 @@ +=begin +File: linear_search.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +require_relative '../utils/list_node' + +### 線形探索(配列) ### +def linear_search_array(nums, target) + # 配列を走査 + for i in 0...nums.length + return i if nums[i] == target # 目標要素が見つかったらそのインデックスを返す + end + + -1 # 目標要素が見つからなければ -1 を返す +end + +### 線形探索(連結リスト) ### +def linear_search_linkedlist(head, target) + # 連結リストを走査 + while head + return head if head.val == target # 対象ノードが見つかったら、それを返す + + head = head.next + end + + nil # 対象ノードが見つからない場合は None を返す +end + +### Driver Code ### +if __FILE__ == $0 + target = 3 + + # 配列で線形探索を行う + nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + index = linear_search_array(nums, target) + puts "対象要素 3 のインデックス = #{index}" + + # 連結リストで線形探索を行う + head = arr_to_linked_list(nums) + node = linear_search_linkedlist(head, target) + puts "対象ノード値 3 に対応するノードオブジェクトは #{node}" +end diff --git a/ja/codes/ruby/chapter_searching/two_sum.rb b/ja/codes/ruby/chapter_searching/two_sum.rb new file mode 100644 index 000000000..f1ef2b049 --- /dev/null +++ b/ja/codes/ruby/chapter_searching/two_sum.rb @@ -0,0 +1,46 @@ +=begin +File: two_sum.rb +Created Time: 2024-04-09 +Author: Blue Bean (lonnnnnnner@gmail.com) +=end + +### 方法1:総当たり列挙 ### +def two_sum_brute_force(nums, target) + # 2重ループのため、時間計算量は O(n^2) + for i in 0...(nums.length - 1) + for j in (i + 1)...nums.length + return [i, j] if nums[i] + nums[j] == target + end + end + + [] +end + +### 方法2:補助ハッシュテーブル ### +def two_sum_hash_table(nums, target) + # 補助ハッシュテーブルを使用し、空間計算量は O(n) + dic = {} + # 単一ループで、時間計算量は O(n) + for i in 0...nums.length + return [dic[target - nums[i]], i] if dic.has_key?(target - nums[i]) + + dic[nums[i]] = i + end + + [] +end + +### Driver Code ### +if __FILE__ == $0 + # ======= Test Case ======= + nums = [2, 7, 11, 15] + target = 13 + + # ====== Driver Code ====== + # 方法 1 + res = two_sum_brute_force(nums, target) + puts "方法1 res = #{res}" + # 方法 2 + res = two_sum_hash_table(nums, target) + puts "方法2 res = #{res}" +end diff --git a/ja/codes/ruby/chapter_sorting/bubble_sort.rb b/ja/codes/ruby/chapter_sorting/bubble_sort.rb new file mode 100644 index 000000000..f835c433f --- /dev/null +++ b/ja/codes/ruby/chapter_sorting/bubble_sort.rb @@ -0,0 +1,51 @@ +=begin +File: bubble_sort.rb +Created Time: 2024-05-02 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### バブルソート ### +def bubble_sort(nums) + n = nums.length + # 外側のループ:未ソート区間は [0, i] + for i in (n - 1).downto(1) + # 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for j in 0...i + if nums[j] > nums[j + 1] + # nums[j] と nums[j + 1] を交換 + nums[j], nums[j + 1] = nums[j + 1], nums[j] + end + end + end +end + +# ## バブルソート(フラグ最適化)### +def bubble_sort_with_flag(nums) + n = nums.length + # 外側のループ:未ソート区間は [0, i] + for i in (n - 1).downto(1) + flag = false # フラグを初期化する + + # 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for j in 0...i + if nums[j] > nums[j + 1] + # nums[j] と nums[j + 1] を交換 + nums[j], nums[j + 1] = nums[j + 1], nums[j] + flag = true # 交換する要素を記録 + end + end + + break unless flag # このバブル処理で要素交換が一度もなければそのまま終了 + end +end + +### Driver Code ### +if __FILE__ == $0 + nums = [4, 1, 3, 1, 5, 2] + bubble_sort(nums) + puts "バブルソート完了後 nums = #{nums}" + + nums1 = [4, 1, 3, 1, 5, 2] + bubble_sort_with_flag(nums1) + puts "バブルソート完了後 nums = #{nums1}" +end diff --git a/ja/codes/ruby/chapter_sorting/bucket_sort.rb b/ja/codes/ruby/chapter_sorting/bucket_sort.rb new file mode 100644 index 000000000..4511978c9 --- /dev/null +++ b/ja/codes/ruby/chapter_sorting/bucket_sort.rb @@ -0,0 +1,43 @@ +=begin +File: bucket_sort.rb +Created Time: 2024-04-17 +Author: Martin Xu (martin.xus@gmail.com) +=end + +### バケットソート ### +def bucket_sort(nums) + # k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする + k = nums.length / 2 + buckets = Array.new(k) { [] } + + # 1. 配列要素を各バケットに振り分ける + nums.each do |num| + # 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する + i = (num * k).to_i + # num をバケット i に追加 + buckets[i] << num + end + + # 2. 各バケットをソートする + buckets.each do |bucket| + # 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい + bucket.sort! + end + + # 3. バケットを走査して結果を結合 + i = 0 + buckets.each do |bucket| + bucket.each do |num| + nums[i] = num + i += 1 + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # 入力データは範囲 [0, 1) の浮動小数点数とする + nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] + bucket_sort(nums) + puts "バケットソート完了後 nums = #{nums}" +end diff --git a/ja/codes/ruby/chapter_sorting/counting_sort.rb b/ja/codes/ruby/chapter_sorting/counting_sort.rb new file mode 100644 index 000000000..d832b00dd --- /dev/null +++ b/ja/codes/ruby/chapter_sorting/counting_sort.rb @@ -0,0 +1,62 @@ +=begin +File: counting_sort.rb +Created Time: 2024-05-02 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 計数ソート ### +def counting_sort_naive(nums) + # 簡易版。オブジェクトのソートには使えない + # 1. 配列の最大要素 m を求める + m = 0 + nums.each { |num| m = [m, num].max } + # 2. 各数値の出現回数を数える + # counter[num] は num の出現回数を表す + counter = Array.new(m + 1, 0) + nums.each { |num| counter[num] += 1 } + # 3. counter を走査し、各要素を元の配列 nums に書き戻す + i = 0 + for num in 0...(m + 1) + (0...counter[num]).each do + nums[i] = num + i += 1 + end + end +end + +### 計数ソート ### +def counting_sort(nums) + # 完全版。オブジェクトをソートでき、かつ安定ソートである + # 1. 配列の最大要素 m を求める + m = nums.max + # 2. 各数値の出現回数を数える + # counter[num] は num の出現回数を表す + counter = Array.new(m + 1, 0) + nums.each { |num| counter[num] += 1 } + # 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する + # つまり counter[num]-1 は、num が res に最後に現れるインデックス + (0...m).each { |i| counter[i + 1] += counter[i] } + # 4. nums を逆順に走査し、各要素を結果配列 res に格納する + # 結果を記録するための配列 res を初期化する + n = nums.length + res = Array.new(n, 0) + (n - 1).downto(0).each do |i| + num = nums[i] + res[counter[num] - 1] = num # num を対応するインデックスに配置 + counter[num] -= 1 # 累積和を 1 減らして、次に num を配置するインデックスを得る + end + # 結果配列 res で元の配列 nums を上書きする + (0...n).each { |i| nums[i] = res[i] } +end + +### Driver Code ### +if __FILE__ == $0 + nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + + counting_sort_naive(nums) + puts "カウントソート(オブジェクトをソートできない)完了後 nums = #{nums}" + + nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + counting_sort(nums1) + puts "カウントソート完了後 nums1 = #{nums1}" +end diff --git a/ja/codes/ruby/chapter_sorting/heap_sort.rb b/ja/codes/ruby/chapter_sorting/heap_sort.rb new file mode 100644 index 000000000..ba0a88ce7 --- /dev/null +++ b/ja/codes/ruby/chapter_sorting/heap_sort.rb @@ -0,0 +1,45 @@ +=begin +File: heap_sort.rb +Created Time: 2024-04-10 +Author: junminhong (junminhong1110@gmail.com) +=end + +### ヒープ長 n で、ノード i から上から下へヒープ化 ### +def sift_down(nums, n, i) + while true + # ノード i, l, r のうち値が最大のノードを ma とする + l = 2 * i + 1 + r = 2 * i + 2 + ma = i + ma = l if l < n && nums[l] > nums[ma] + ma = r if r < n && nums[r] > nums[ma] + # ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + break if ma == i + # 2 つのノードを交換 + nums[i], nums[ma] = nums[ma], nums[i] + # ループで上から下へヒープ化 + i = ma + end +end + +### ヒープソート ### +def heap_sort(nums) + # ヒープ構築:葉ノード以外のすべてのノードをヒープ化する + (nums.length / 2 - 1).downto(0) do |i| + sift_down(nums, nums.length, i) + end + # ヒープから最大要素を取り出し、n-1 回繰り返す + (nums.length - 1).downto(1) do |i| + # 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + nums[0], nums[i] = nums[i], nums[0] + # 根ノードを起点に、上から下へヒープ化 + sift_down(nums, i, 0) + end +end + +### Driver Code ### +if __FILE__ == $0 + nums = [4, 1, 3, 1, 5, 2] + heap_sort(nums) + puts "ヒープソート完了後 nums = #{nums.inspect}" +end diff --git a/ja/codes/ruby/chapter_sorting/insertion_sort.rb b/ja/codes/ruby/chapter_sorting/insertion_sort.rb new file mode 100644 index 000000000..01143bec9 --- /dev/null +++ b/ja/codes/ruby/chapter_sorting/insertion_sort.rb @@ -0,0 +1,26 @@ +=begin +File: insertion_sort.rb +Created Time: 2024-04-02 +Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 挿入ソート ### +def insertion_sort(nums) + n = nums.length + # 外側ループ:整列済み区間は [0, i-1] + for i in 1...n + base = nums[i] + j = i - 1 + # 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する + while j >= 0 && nums[j] > base + nums[j + 1] = nums[j] # nums[j] を 1 つ右へ移動する + j -= 1 + end + nums[j + 1] = base # base を正しい位置に配置する + end +end + +### Driver Code ### +nums = [4, 1, 3, 1, 5, 2] +insertion_sort(nums) +puts "挿入ソート完了後 nums = #{nums}" diff --git a/ja/codes/ruby/chapter_sorting/merge_sort.rb b/ja/codes/ruby/chapter_sorting/merge_sort.rb new file mode 100644 index 000000000..e5afb7a42 --- /dev/null +++ b/ja/codes/ruby/chapter_sorting/merge_sort.rb @@ -0,0 +1,60 @@ +=begin +File: merge_sort.rb +Created Time: 2024-04-10 +Author: junminhong (junminhong1110@gmail.com) +=end + +### 左部分配列と右部分配列をマージ ### +def merge(nums, left, mid, right) + # 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] + # マージ結果を格納する一時配列 tmp を作成 + tmp = Array.new(right - left + 1, 0) + # 左右の部分配列の開始インデックスを初期化する + i, j, k = left, mid + 1, 0 + # 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする + while i <= mid && j <= right + if nums[i] <= nums[j] + tmp[k] = nums[i] + i += 1 + else + tmp[k] = nums[j] + j += 1 + end + k += 1 + end + # 左右の部分配列の残り要素を一時配列にコピーする + while i <= mid + tmp[k] = nums[i] + i += 1 + k += 1 + end + while j <= right + tmp[k] = nums[j] + j += 1 + k += 1 + end + # 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする + (0...tmp.length).each do |k| + nums[left + k] = tmp[k] + end +end + +### マージソート ### +def merge_sort(nums, left, right) + # 終了条件 + # 部分配列の長さが 1 になったら再帰を終了する + return if left >= right + # 分割フェーズ + mid = left + (right - left) / 2 # 中点を計算 + merge_sort(nums, left, mid) # 左部分配列を再帰処理 + merge_sort(nums, mid + 1, right) # 右部分配列を再帰処理 + # マージフェーズ + merge(nums, left, mid, right) +end + +### Driver Code ### +if __FILE__ == $0 + nums = [7, 3, 2, 6, 0, 1, 5, 4] + merge_sort(nums, 0, nums.length - 1) + puts "マージソート完了後 nums = #{nums.inspect}" +end diff --git a/ja/codes/ruby/chapter_sorting/quick_sort.rb b/ja/codes/ruby/chapter_sorting/quick_sort.rb new file mode 100644 index 000000000..e4f9cafe2 --- /dev/null +++ b/ja/codes/ruby/chapter_sorting/quick_sort.rb @@ -0,0 +1,153 @@ +=begin +File: quick_sort.rb +Created Time: 2024-04-01 +Author: Cy (3739004@gmail.com), Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### クイックソートクラス ### +class QuickSort + class << self + ### 番兵分割 ### + def partition(nums, left, right) + # nums[left] を基準値とする + i, j = left, right + while i < j + while i < j && nums[j] >= nums[left] + j -= 1 # 右から左へ基準値未満の最初の要素を探す + end + while i < j && nums[i] <= nums[left] + i += 1 # 左から右へ基準値より大きい最初の要素を探す + end + # 要素の交換 + nums[i], nums[j] = nums[j], nums[i] + end + # 基準値を 2 つの部分配列の境界へ交換する + nums[i], nums[left] = nums[left], nums[i] + i # 基準値のインデックスを返す + end + + ### クイックソートクラス ### + def quick_sort(nums, left, right) + # 部分配列の長さが 1 でない場合は再帰する + if left < right + # 番兵分割 + pivot = partition(nums, left, right) + # 左右の部分配列を再帰処理 + quick_sort(nums, left, pivot - 1) + quick_sort(nums, pivot + 1, right) + end + nums + end + end +end + +# ## クイックソートクラス(中央値最適化)### +class QuickSortMedian + class << self + ### 3 つの候補要素の中央値を選ぶ ### + def median_three(nums, left, mid, right) + # 3つの候補要素の中央値を選ぶ + _l, _m, _r = nums[left], nums[mid], nums[right] + # m は l と r の間 + return mid if (_l <= _m && _m <= _r) || (_r <= _m && _m <= _l) + # l は m と r の間 + return left if (_m <= _l && _l <= _r) || (_r <= _l && _l <= _m) + return right + end + + # ## 番兵分割(三数中央値)### + def partition(nums, left, right) + # ## nums[left] を基準値とする + med = median_three(nums, left, (left + right) / 2, right) + # 中央値を配列の最左端に交換する + nums[left], nums[med] = nums[med], nums[left] + i, j = left, right + while i < j + while i < j && nums[j] >= nums[left] + j -= 1 # 右から左へ基準値未満の最初の要素を探す + end + while i < j && nums[i] <= nums[left] + i += 1 # 左から右へ基準値より大きい最初の要素を探す + end + # 要素の交換 + nums[i], nums[j] = nums[j], nums[i] + end + # 基準値を 2 つの部分配列の境界へ交換する + nums[i], nums[left] = nums[left], nums[i] + i # 基準値のインデックスを返す + end + + ### クイックソート ### + def quick_sort(nums, left, right) + # 部分配列の長さが 1 でない場合は再帰する + if left < right + # 番兵分割 + pivot = partition(nums, left, right) + # 左右の部分配列を再帰処理 + quick_sort(nums, left, pivot - 1) + quick_sort(nums, pivot + 1, right) + end + nums + end + end +end + +# ## クイックソートクラス(再帰深度最適化)### +class QuickSortTailCall + class << self + ### 番兵分割 ### + def partition(nums, left, right) + # nums[left] を基準値とする + i = left + j = right + while i < j + while i < j && nums[j] >= nums[left] + j -= 1 # 右から左へ基準値未満の最初の要素を探す + end + while i < j && nums[i] <= nums[left] + i += 1 # 左から右へ基準値より大きい最初の要素を探す + end + # 要素の交換 + nums[i], nums[j] = nums[j], nums[i] + end + # 基準値を 2 つの部分配列の境界へ交換する + nums[i], nums[left] = nums[left], nums[i] + i # 基準値のインデックスを返す + end + + # ## クイックソート(再帰深度最適化)### + def quick_sort(nums, left, right) + # 部分配列の長さが 1 でない場合は再帰する + while left < right + # 番兵分割 + pivot = partition(nums, left, right) + # 2 つの部分配列のうち短いほうにクイックソートを適用する + if pivot - left < right - pivot + quick_sort(nums, left, pivot - 1) + left = pivot + 1 # 未ソート区間の残りは [pivot + 1, right] + else + quick_sort(nums, pivot + 1, right) + right = pivot - 1 # 未ソート区間の残りは [left, pivot - 1] + end + end + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # クイックソート + nums = [2, 4, 1, 0, 3, 5] + QuickSort.quick_sort(nums, 0, nums.length - 1) + puts "クイックソート完了後 nums = #{nums}" + + # クイックソート(中央値の基準値で最適化) + nums1 = [2, 4, 1, 0, 3, 5] + QuickSortMedian.quick_sort(nums1, 0, nums1.length - 1) + puts "クイックソート(中央値ピボット最適化)完了後 nums1 = #{nums1}" + + # クイックソート(再帰深度最適化) + nums2 = [2, 4, 1, 0, 3, 5] + QuickSortTailCall.quick_sort(nums2, 0, nums2.length - 1) + puts "クイックソート(再帰深度最適化)完了後 nums2 = #{nums2}" +end diff --git a/ja/codes/ruby/chapter_sorting/radix_sort.rb b/ja/codes/ruby/chapter_sorting/radix_sort.rb new file mode 100644 index 000000000..94b201fc6 --- /dev/null +++ b/ja/codes/ruby/chapter_sorting/radix_sort.rb @@ -0,0 +1,70 @@ +=begin +File: radix_sort.rb +Created Time: 2024-05-03 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### num の第 k 桁を取得する。ここで exp = 10^(k-1) ### +def digit(num, exp) + # k ではなく exp を渡すことで、ここで高コストな累乗計算を繰り返し実行するのを避けられる + (num / exp) % 10 +end + +# ## 計数ソート(nums の k 桁目でソート)### +def counting_sort_digit(nums, exp) + # 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 + counter = Array.new(10, 0) + n = nums.length + # 0~9 の各数字の出現回数を集計する + for i in 0...n + d = digit(nums[i], exp) # nums[i] の第 k 位を取得し、d とする + counter[d] += 1 # 数字 d の出現回数を数える + end + # 累積和を求め、「出現回数」を「配列インデックス」に変換する + (1...10).each { |i| counter[i] += counter[i - 1] } + # 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する + res = Array.new(n, 0) + for i in (n - 1).downto(0) + d = digit(nums[i], exp) + j = counter[d] - 1 # d の配列内インデックス j を取得する + res[j] = nums[i] # 現在の要素をインデックス j に格納する + counter[d] -= 1 # d の個数を 1 減らす + end + # 結果で元の配列 nums を上書きする + (0...n).each { |i| nums[i] = res[i] } +end + +### 基数ソート ### +def radix_sort(nums) + # 最大桁数の判定用に配列の最大要素を取得 + m = nums.max + # 下位桁から上位桁の順に走査する + exp = 1 + while exp <= m + # 配列要素の k 桁目に対して計数ソートを行う + # k = 1 -> exp = 1 + # k = 2 -> exp = 10 + # つまり exp = 10^(k-1) + counting_sort_digit(nums, exp) + exp *= 10 + end +end + +### Driver Code ### +if __FILE__ == $0 + # 基数ソート + nums = [ + 10546151, + 35663510, + 42865989, + 34862445, + 81883077, + 88906420, + 72429244, + 30524779, + 82060337, + 63832996, + ] + radix_sort(nums) + puts "基数ソート完了後 nums = #{nums}" +end diff --git a/ja/codes/ruby/chapter_sorting/selection_sort.rb b/ja/codes/ruby/chapter_sorting/selection_sort.rb new file mode 100644 index 000000000..97909d310 --- /dev/null +++ b/ja/codes/ruby/chapter_sorting/selection_sort.rb @@ -0,0 +1,29 @@ +=begin +File: selection_sort.rb +Created Time: 2024-05-03 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 選択ソート ### +def selection_sort(nums) + n = nums.length + # 外側ループ:未整列区間は [i, n-1] + for i in 0...(n - 1) + # 内側のループ:未ソート区間の最小要素を見つける + k = i + for j in (i + 1)...n + if nums[j] < nums[k] + k = j # 最小要素のインデックスを記録 + end + end + # その最小要素を未整列区間の先頭要素と交換する + nums[i], nums[k] = nums[k], nums[i] + end +end + +### Driver Code ### +if __FILE__ == $0 + nums = [4, 1, 3, 1, 5, 2] + selection_sort(nums) + puts "選択ソート完了後 nums = #{nums}" +end diff --git a/ja/codes/ruby/chapter_stack_and_queue/array_deque.rb b/ja/codes/ruby/chapter_stack_and_queue/array_deque.rb new file mode 100644 index 000000000..927e60c79 --- /dev/null +++ b/ja/codes/ruby/chapter_stack_and_queue/array_deque.rb @@ -0,0 +1,145 @@ +=begin +File: array_deque.rb +Created Time: 2024-04-05 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 循環配列で実装した両端キュー ### +class ArrayDeque + ### 両端キューの長さを取得 ### + attr_reader :size + + ### コンストラクタ ### + def initialize(capacity) + @nums = Array.new(capacity, 0) + @front = 0 + @size = 0 + end + + ### 両端キューの容量を取得 ### + def capacity + @nums.length + end + + ### 両端キューが空か判定 ### + def is_empty? + size.zero? + end + + ### キュー先頭に追加 ### + def push_first(num) + if size == capacity + puts '両端キューがいっぱいです' + return + end + + # 先頭ポインタを左に 1 つ移動する + # 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする + @front = index(@front - 1) + # num をキュー先頭に追加 + @nums[@front] = num + @size += 1 + end + + ### キュー末尾に追加 ### + def push_last(num) + if size == capacity + puts '両端キューがいっぱいです' + return + end + + # キュー末尾ポインタを計算し、末尾インデックス + 1 を指す + rear = index(@front + size) + # num をキュー末尾に追加 + @nums[rear] = num + @size += 1 + end + + ### キュー先頭から取り出す ### + def pop_first + num = peek_first + # 先頭ポインタを 1 つ後ろへ進める + @front = index(@front + 1) + @size -= 1 + num + end + + ### キューの末尾から取り出す ### + def pop_last + num = peek_last + @size -= 1 + num + end + + ### 先頭要素にアクセス ### + def peek_first + raise IndexError, '両端キューは空です' if is_empty? + + @nums[@front] + end + + ### キュー末尾要素を参照 ### + def peek_last + raise IndexError, '両端キューは空です' if is_empty? + + # 末尾要素のインデックスを計算 + last = index(@front + size - 1) + @nums[last] + end + + ### 表示用の配列を返す ### + def to_array + # 有効長の範囲内のリスト要素のみを変換 + res = [] + for i in 0...size + res << @nums[index(@front + i)] + end + res + end + + private + + ### 循環配列のインデックスを計算 ### + def index(i) + # 剰余演算により配列の先頭と末尾をつなげる + # i が配列の末尾を越えたら先頭に戻る + # i が配列の先頭を越えて前に出たら末尾に戻る + (i + capacity) % capacity + end +end + +### Driver Code ### +if __FILE__ == $0 + # 両端キューを初期化 + deque = ArrayDeque.new(10) + deque.push_last(3) + deque.push_last(2) + deque.push_last(5) + puts "両端キュー deque = #{deque.to_array}" + + # 要素にアクセス + peek_first = deque.peek_first + puts "先頭要素 peek_first = #{peek_first}" + peek_last = deque.peek_last + puts "末尾要素 peek_last = #{peek_last}" + + # 要素をエンキュー + deque.push_last(4) + puts "要素 4 を末尾に追加後 deque = #{deque.to_array}" + deque.push_first(1) + puts "要素 1 を末尾に追加後 deque = #{deque.to_array}" + + # 要素をデキュー + pop_last = deque.pop_last + puts "末尾から取り出した要素 = #{pop_last},末尾から取り出した後 deque = #{deque.to_array}" + pop_first = deque.pop_first + puts "末尾から取り出した要素 = #{pop_first},末尾から取り出した後 deque = #{deque.to_array}" + + # 両端キューの長さを取得 + size = deque.size + puts "両端キューの長さ size = #{size}" + + # 両端キューが空かどうかを判定 + is_empty = deque.is_empty? + puts "両端キューが空かどうか = #{is_empty}" +end diff --git a/ja/codes/ruby/chapter_stack_and_queue/array_queue.rb b/ja/codes/ruby/chapter_stack_and_queue/array_queue.rb new file mode 100644 index 000000000..6430b51bd --- /dev/null +++ b/ja/codes/ruby/chapter_stack_and_queue/array_queue.rb @@ -0,0 +1,107 @@ +=begin +File: array_queue.rb +Created Time: 2024-04-05 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 循環配列で実装したキュー ### +class ArrayQueue + ### キューの長さを取得 ### + attr_reader :size + + ### コンストラクタ ### + def initialize(size) + @nums = Array.new(size, 0) # キュー要素を格納する配列 + @front = 0 # 先頭ポインタ。先頭要素を指す + @size = 0 # キューの長さ + end + + ### キューの容量を取得 ### + def capacity + @nums.length + end + + ### キューが空か判定 ### + def is_empty? + size.zero? + end + + ### エンキュー ### + def push(num) + raise IndexError, 'キューがいっぱいです' if size == capacity + + # 末尾ポインタを計算し、末尾インデックス + 1 を指す + # 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする + rear = (@front + size) % capacity + # num をキュー末尾に追加 + @nums[rear] = num + @size += 1 + end + + ### デキュー ### + def pop + num = peek + # 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す + @front = (@front + 1) % capacity + @size -= 1 + num + end + + ### 先頭要素にアクセス ### + def peek + raise IndexError, 'キューは空です' if is_empty? + + @nums[@front] + end + + ### 表示用のリストを返す ### + def to_array + res = Array.new(size, 0) + j = @front + + for i in 0...size + res[i] = @nums[j % capacity] + j += 1 + end + + res + end +end + +### Driver Code ### +if __FILE__ == $0 + # キューを初期化 + queue = ArrayQueue.new(10) + + # 要素をエンキュー + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + puts "キュー queue = #{queue.to_array}" + + # キュー先頭の要素にアクセス + peek = queue.peek + puts "先頭要素 peek = #{peek}" + + # 要素をデキュー + pop = queue.pop + puts "取り出した要素 pop = #{pop}" + puts "取り出し後 queue = #{queue.to_array}" + + # キューの長さを取得 + size = queue.size + puts "キューの長さ size = #{size}" + + # キューが空かどうかを判定 + is_empty = queue.is_empty? + puts "キューが空かどうか = #{is_empty}" + + # 循環配列をテストする + for i in 0...10 + queue.push(i) + queue.pop + puts "第 #{i} 回の追加 + 取り出し後 queue = #{queue.to_array}" + end +end diff --git a/ja/codes/ruby/chapter_stack_and_queue/array_stack.rb b/ja/codes/ruby/chapter_stack_and_queue/array_stack.rb new file mode 100644 index 000000000..1e5e438a6 --- /dev/null +++ b/ja/codes/ruby/chapter_stack_and_queue/array_stack.rb @@ -0,0 +1,78 @@ +=begin +File: array_stack.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 配列で実装したスタック ### +class ArrayStack + ### コンストラクタ ### + def initialize + @stack = [] + end + + ### スタックの長さを取得 ### + def size + @stack.length + end + + ### スタックが空か判定 ### + def is_empty? + @stack.empty? + end + + ### プッシュ ### + def push(item) + @stack << item + end + + ### ポップ ### + def pop + raise IndexError, 'スタックは空です' if is_empty? + + @stack.pop + end + + ### スタックトップ要素を参照 ### + def peek + raise IndexError, 'スタックは空です' if is_empty? + + @stack.last + end + + ### 表示用のリストを返す ### + def to_array + @stack + end +end + +### Driver Code ### +if __FILE__ == $0 + # スタックを初期化 + stack = ArrayStack.new + + # 要素をプッシュ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + puts "スタック stack = #{stack.to_array}" + + # スタックトップの要素にアクセス + peek = stack.peek + puts "スタックトップ要素 peek = #{peek}" + + # 要素をポップ + pop = stack.pop + puts "ポップした要素 pop = #{pop}" + puts "ポップ後 stack = #{stack.to_array}" + + # スタックの長さを取得 + size = stack.size + puts "スタックの長さ size = #{size}" + + # 空かどうかを判定 + is_empty = stack.is_empty? + puts "スタックが空かどうか = #{is_empty}" +end diff --git a/ja/codes/ruby/chapter_stack_and_queue/deque.rb b/ja/codes/ruby/chapter_stack_and_queue/deque.rb new file mode 100644 index 000000000..4feede2dc --- /dev/null +++ b/ja/codes/ruby/chapter_stack_and_queue/deque.rb @@ -0,0 +1,42 @@ +=begin +File: deque.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### Driver Code ### +if __FILE__ == $0 + # 両端キューを初期化する + # Ruby には組み込みの両端キューがないため、Array を両端キューとして使う + deque = [] + + # 要素をキューに入れる + deque << 2 + deque << 5 + deque << 4 + # 注意:配列であるため、Array#unshift メソッドの時間計算量は O(n) です + deque.unshift(3) + deque.unshift(1) + puts "両端キュー deque = #{deque}" + + # 要素にアクセス + peek_first = deque.first + puts "先頭要素 peek_first = #{peek_first}" + peek_last = deque.last + puts "末尾要素 peek_last = #{peek_last}" + + # 要素をキューから取り出す + # 配列であるため、Array#shift メソッドの時間計算量は O(n) である + pop_front = deque.shift + puts "先頭から取り出した要素 pop_front = #{pop_front},先頭から取り出した後 deque = #{deque}" + pop_back = deque.pop + puts "末尾から取り出した要素 pop_back = #{pop_back}, 末尾から取り出した後 deque = #{deque}" + + # 両端キューの長さを取得 + size = deque.length + puts "両端キューの長さ size = #{size}" + + # 両端キューが空かどうかを判定 + is_empty = size.zero? + puts "両端キューが空かどうか = #{is_empty}" +end diff --git a/ja/codes/ruby/chapter_stack_and_queue/linkedlist_deque.rb b/ja/codes/ruby/chapter_stack_and_queue/linkedlist_deque.rb new file mode 100644 index 000000000..11519f74b --- /dev/null +++ b/ja/codes/ruby/chapter_stack_and_queue/linkedlist_deque.rb @@ -0,0 +1,168 @@ +=begin +File: linkedlist_deque.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +# ## 双方向連結リストノード +class ListNode + attr_accessor :val + attr_accessor :next # 後続ノードへの参照 + attr_accessor :prev # 前ノードへの参照 + + ### コンストラクタ ### + def initialize(val) + @val = val + end +end + +### 双方向連結リストで実装した両端キュー ### +class LinkedListDeque + ### 両端キューの長さを取得 ### + attr_reader :size + + ### コンストラクタ ### + def initialize + @front = nil # 先頭ノード front + @rear = nil # 末尾ノード rear + @size = 0 # 両端キューの長さ + end + + ### 両端キューが空か判定 ### + def is_empty? + size.zero? + end + + ### エンキュー操作 ### + def push(num, is_front) + node = ListNode.new(num) + # 連結リストが空なら、`front` と `rear` の両方を `node` に向ける + if is_empty? + @front = @rear = node + # 先頭へのエンキュー操作 + elsif is_front + # node を連結リストの先頭に追加 + @front.prev = node + node.next = @front + @front = node # 先頭ノードを更新する + # 末尾へのエンキュー操作 + else + # node を連結リストの末尾に追加 + @rear.next = node + node.prev = @rear + @rear = node # 末尾ノードを更新する + end + @size += 1 # キューの長さを更新 + end + + ### キュー先頭に追加 ### + def push_first(num) + push(num, true) + end + + ### キュー末尾に追加 ### + def push_last(num) + push(num, false) + end + + ### デキュー操作 ### + def pop(is_front) + raise IndexError, '両端キューは空です' if is_empty? + + # キュー先頭からの取り出し + if is_front + val = @front.val # 先頭ノードの値を一時保存 + # 先頭ノードを削除 + fnext = @front.next + unless fnext.nil? + fnext.prev = nil + @front.next = nil + end + @front = fnext # 先頭ノードを更新する + # キュー末尾からの取り出し + else + val = @rear.val # 末尾ノードの値を一時保存 + # 末尾ノードを削除 + rprev = @rear.prev + unless rprev.nil? + rprev.next = nil + @rear.prev = nil + end + @rear = rprev # 末尾ノードを更新する + end + @size -= 1 # キューの長さを更新 + + val + end + + ### キュー先頭から取り出す ### + def pop_first + pop(true) + end + + ### キュー先頭から取り出す ### + def pop_last + pop(false) + end + + ### 先頭要素にアクセス ### + def peek_first + raise IndexError, '両端キューは空です' if is_empty? + + @front.val + end + + ### キュー末尾要素を参照 ### + def peek_last + raise IndexError, '両端キューは空です' if is_empty? + + @rear.val + end + + ### 表示用の配列を返す ### + def to_array + node = @front + res = Array.new(size, 0) + for i in 0...size + res[i] = node.val + node = node.next + end + res + end +end + +### Driver Code ### +if __FILE__ == $0 + # 両端キューを初期化 + deque = LinkedListDeque.new + deque.push_last(3) + deque.push_last(2) + deque.push_last(5) + puts "両端キュー deque = #{deque.to_array}" + + # 要素にアクセス + peek_first = deque.peek_first + puts "先頭要素 peek_first = #{peek_first}" + peek_last = deque.peek_last + puts "先頭要素 peek_last = #{peek_last}" + + # 要素をエンキュー + deque.push_last(4) + puts "要素 4 を末尾に追加後 deque = #{deque.to_array}" + deque.push_first(1) + puts "要素 1 を先頭に追加後 deque = #{deque.to_array}" + + # 要素をデキュー + pop_last = deque.pop_last + puts "末尾から取り出した要素 = #{pop_last}, 末尾から取り出した後 deque = #{deque.to_array}" + pop_first = deque.pop_first + puts "先頭から取り出した要素 = #{pop_first},先頭から取り出した後 deque = #{deque.to_array}" + + # 両端キューの長さを取得 + size = deque.size + puts "両端キューの長さ size = #{size}" + + # 両端キューが空かどうかを判定 + is_empty = deque.is_empty? + puts "両端キューが空かどうか = #{is_empty}" +end diff --git a/ja/codes/ruby/chapter_stack_and_queue/linkedlist_queue.rb b/ja/codes/ruby/chapter_stack_and_queue/linkedlist_queue.rb new file mode 100644 index 000000000..ef0378434 --- /dev/null +++ b/ja/codes/ruby/chapter_stack_and_queue/linkedlist_queue.rb @@ -0,0 +1,101 @@ +=begin +File: linkedlist_queue.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' + +### 連結リストで実装したキュー ### +class LinkedListQueue + ### キューの長さを取得 ### + attr_reader :size + + ### コンストラクタ ### + def initialize + @front = nil # 先頭ノード front + @rear = nil # 末尾ノード rear + @size = 0 + end + + ### キューが空か判定 ### + def is_empty? + @front.nil? + end + + ### エンキュー ### + def push(num) + # 末尾ノードの後ろに num を追加 + node = ListNode.new(num) + + # キューが空なら、先頭ノードと末尾ノードの両方をそのノードに向ける + if @front.nil? + @front = node + @rear = node + # キューが空でなければ、そのノードを末尾ノードの後ろに追加する + else + @rear.next = node + @rear = node + end + + @size += 1 + end + + ### デキュー ### + def pop + num = peek + # 先頭ノードを削除 + @front = @front.next + @size -= 1 + num + end + + ### 先頭要素にアクセス ### + def peek + raise IndexError, 'キューは空です' if is_empty? + + @front.val + end + + ### 連結リストを Array に変換して返す ### + def to_array + queue = [] + temp = @front + while temp + queue << temp.val + temp = temp.next + end + queue + end +end + +### Driver Code ### +if __FILE__ == $0 + # キューを初期化 + queue = LinkedListQueue.new + + # 要素をキューに入れる + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + puts "キュー queue = #{queue.to_array}" + + # キュー先頭の要素にアクセス + peek = queue.peek + puts "先頭要素 front = #{peek}" + + # 要素をデキュー + pop_front = queue.pop + puts "取り出した要素 pop = #{pop_front}" + puts "取り出し後 queue = #{queue.to_array}" + + # キューの長さを取得 + size = queue.size + puts "キューの長さ size = #{size}" + + # キューが空かどうかを判定 + is_empty = queue.is_empty? + puts "キューが空かどうか = #{is_empty}" +end diff --git a/ja/codes/ruby/chapter_stack_and_queue/linkedlist_stack.rb b/ja/codes/ruby/chapter_stack_and_queue/linkedlist_stack.rb new file mode 100644 index 000000000..45100b299 --- /dev/null +++ b/ja/codes/ruby/chapter_stack_and_queue/linkedlist_stack.rb @@ -0,0 +1,87 @@ +=begin +File: linkedlist_stack.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/list_node' + +### 連結リストで実装したスタック ### +class LinkedListStack + attr_reader :size + + ### コンストラクタ ### + def initialize + @size = 0 + end + + ### スタックが空か判定 ### + def is_empty? + @peek.nil? + end + + ### プッシュ ### + def push(val) + node = ListNode.new(val) + node.next = @peek + @peek = node + @size += 1 + end + + ### ポップ ### + def pop + num = peek + @peek = @peek.next + @size -= 1 + num + end + + ### スタックトップ要素を参照 ### + def peek + raise IndexError, 'スタックは空です' if is_empty? + + @peek.val + end + + ### 連結リストを Array に変換して返す ### + def to_array + arr = [] + node = @peek + while node + arr << node.val + node = node.next + end + arr.reverse + end +end + +### Driver Code ### +if __FILE__ == $0 + # スタックを初期化 + stack = LinkedListStack.new + + # 要素をプッシュ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + puts "スタック stack = #{stack.to_array}" + + # スタックトップの要素にアクセス + peek = stack.peek + puts "スタックトップ要素 peek = #{peek}" + + # 要素をポップ + pop = stack.pop + puts "ポップした要素 pop = #{pop}" + puts "ポップ後 stack = #{stack.to_array}" + + # スタックの長さを取得 + size = stack.size + puts "スタックの長さ size = #{size}" + + # 空かどうかを判定 + is_empty = stack.is_empty? + puts "スタックが空かどうか = #{is_empty}" +end diff --git a/ja/codes/ruby/chapter_stack_and_queue/queue.rb b/ja/codes/ruby/chapter_stack_and_queue/queue.rb new file mode 100644 index 000000000..0aa6bad8b --- /dev/null +++ b/ja/codes/ruby/chapter_stack_and_queue/queue.rb @@ -0,0 +1,38 @@ +=begin +File: queue.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### Driver Code ### +if __FILE__ == $0 + # キューを初期化する + # Ruby 組み込みのキュー(Thread::Queue)には peek と走査メソッドがないため、Array をキューとして使える + queue = [] + + # 要素をエンキュー + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + puts "キュー queue = #{queue}" + + # キューの要素にアクセス + peek = queue.first + puts "先頭要素 peek = #{peek}" + + # 要素をキューから取り出す + # 配列であるため、Array#shift メソッドの時間計算量は O(n) である + pop = queue.shift + puts "取り出した要素 pop = #{pop}" + puts "取り出し後 queue = #{queue}" + + # キューの長さを取得 + size = queue.length + puts "キューの長さ size = #{size}" + + # キューが空かどうかを判定 + is_empty = queue.empty? + puts "キューが空かどうか = #{is_empty}" +end diff --git a/ja/codes/ruby/chapter_stack_and_queue/stack.rb b/ja/codes/ruby/chapter_stack_and_queue/stack.rb new file mode 100644 index 000000000..ac7694b59 --- /dev/null +++ b/ja/codes/ruby/chapter_stack_and_queue/stack.rb @@ -0,0 +1,37 @@ +=begin +File: stack.rb +Created Time: 2024-04-06 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### Driver Code ### +if __FILE__ == $0 + # スタックを初期化する + # Ruby には組み込みのスタッククラスがないため、Array をスタックとして使える + stack = [] + + # 要素をプッシュ + stack << 1 + stack << 3 + stack << 2 + stack << 5 + stack << 4 + puts "スタック stack = #{stack}" + + # スタックトップの要素にアクセス + peek = stack.last + puts "スタックトップ要素 peek = #{peek}" + + # 要素をポップ + pop = stack.pop + puts "ポップした要素 pop = #{pop}" + puts "ポップ後 stack = #{stack}" + + # スタックの長さを取得 + size = stack.length + puts "スタックの長さ size = #{size}" + + # 空かどうかを判定 + is_empty = stack.empty? + puts "スタックが空かどうか = #{is_empty}" +end diff --git a/ja/codes/ruby/chapter_tree/array_binary_tree.rb b/ja/codes/ruby/chapter_tree/array_binary_tree.rb new file mode 100644 index 000000000..73f8957db --- /dev/null +++ b/ja/codes/ruby/chapter_tree/array_binary_tree.rb @@ -0,0 +1,124 @@ +=begin +File: array_binary_tree.rb +Created Time: 2024-04-17 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 配列表現の二分木クラス ### +class ArrayBinaryTree + ### コンストラクタ ### + def initialize(arr) + @tree = arr.to_a + end + + ### リスト容量 ### + def size + @tree.length + end + + ### インデックス i のノードの値を取得 ### + def val(i) + # インデックスが範囲外なら `nil` を返し、空きスロットを表す + return if i < 0 || i >= size + + @tree[i] + end + + ### インデックス i のノードの左子ノードのインデックスを取得 ### + def left(i) + 2 * i + 1 + end + + ### インデックス i のノードの右子ノードのインデックスを取得 ### + def right(i) + 2 * i + 2 + end + + ### インデックス i のノードの親ノードのインデックスを取得 ### + def parent(i) + (i - 1) / 2 + end + + ### レベル順走査 ### + def level_order + @res = [] + + # 配列を直接走査する + for i in 0...size + @res << val(i) unless val(i).nil? + end + + @res + end + + ### 深さ優先探索 ### + def dfs(i, order) + return if val(i).nil? + # 先行順走査 + @res << val(i) if order == :pre + dfs(left(i), order) + # 中順走査 + @res << val(i) if order == :in + dfs(right(i), order) + # 後順走査 + @res << val(i) if order == :post + end + + ### 前順走査 ### + def pre_order + @res = [] + dfs(0, :pre) + @res + end + + ### 中順走査 ### + def in_order + @res = [] + dfs(0, :in) + @res + end + + ### 後順走査 ### + def post_order + @res = [] + dfs(0, :post) + @res + end +end + +### Driver Code ### +if __FILE__ == $0 + # 二分木を初期化 + # ここでは、配列から直接二分木を生成する関数を利用する + arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + root = arr_to_tree(arr) + puts "\n二分木を初期化\n\n" + puts '二分木の配列表現:' + pp arr + puts '二分木の連結リスト表現:' + print_tree(root) + + # 配列表現による二分木クラス + abt = ArrayBinaryTree.new(arr) + + # ノードにアクセス + i = 1 + l, r, _p = abt.left(i), abt.right(i), abt.parent(i) + puts "\n現在のノードのインデックスは #{i} ,値は #{abt.val(i).inspect}" + puts "左の子ノードのインデックスは #{l} ,値は #{abt.val(l).inspect}" + puts "右の子ノードのインデックスは #{r} ,値は #{abt.val(r).inspect}" + puts "親ノードのインデックスは #{_p} ,値は #{abt.val(_p).inspect}" + + # 木を走査 + res = abt.level_order + puts "\nレベル順走査の結果: #{res}" + res = abt.pre_order + puts "前順走査の結果: #{res}" + res = abt.in_order + puts "中順走査の結果: #{res}" + res = abt.post_order + puts "後順走査の結果: #{res}" +end diff --git a/ja/codes/ruby/chapter_tree/avl_tree.rb b/ja/codes/ruby/chapter_tree/avl_tree.rb new file mode 100644 index 000000000..61d00dd8e --- /dev/null +++ b/ja/codes/ruby/chapter_tree/avl_tree.rb @@ -0,0 +1,216 @@ +=begin +File: avl_tree.rb +Created Time: 2024-04-17 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### AVL 木 ### +class AVLTree + ### コンストラクタ ### + def initialize + @root = nil + end + + ### 二分木の根ノードを取得 ### + def get_root + @root + end + + ### ノードの高さを取得 ### + def height(node) + # 空ノードの高さは -1、葉ノードの高さは 0 + return node.height unless node.nil? + + -1 + end + + ### ノードの高さを更新 ### + def update_height(node) + # ノードの高さは最も高い部分木の高さ + 1 に等しい + node.height = [height(node.left), height(node.right)].max + 1 + end + + ### 平衡係数を取得 ### + def balance_factor(node) + # 空ノードの平衡係数は 0 + return 0 if node.nil? + + # ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ + height(node.left) - height(node.right) + end + + ### 右回転操作 ### + def right_rotate(node) + child = node.left + grand_child = child.right + # child を支点として node を右回転させる + child.right = node + node.left = grand_child + # ノードの高さを更新する + update_height(node) + update_height(child) + # 回転後の部分木の根ノードを返す + child + end + + ### 左回転操作 ### + def left_rotate(node) + child = node.right + grand_child = child.left + # child を支点として node を左回転させる + child.left = node + node.right = grand_child + # ノードの高さを更新する + update_height(node) + update_height(child) + # 回転後の部分木の根ノードを返す + child + end + + ### 回転操作を行い、この部分木の平衡を回復する ### + def rotate(node) + # ノード node の平衡係数を取得 + balance_factor = balance_factor(node) + # 左部分木をたどる + if balance_factor > 1 + if balance_factor(node.left) >= 0 + # 右回転 + return right_rotate(node) + else + # 左回転してから右回転 + node.left = left_rotate(node.left) + return right_rotate(node) + end + # 右に偏った木 + elsif balance_factor < -1 + if balance_factor(node.right) <= 0 + # 左回転 + return left_rotate(node) + else + # 右回転してから左回転 + node.right = right_rotate(node.right) + return left_rotate(node) + end + end + # 平衡木なので回転不要、そのまま返す + node + end + + ### ノードを挿入 ### + def insert(val) + @root = insert_helper(@root, val) + end + + # ## ノードを再帰的に挿入(補助メソッド)### + def insert_helper(node, val) + return TreeNode.new(val) if node.nil? + # 1. 挿入位置を探索してノードを挿入 + if val < node.val + node.left = insert_helper(node.left, val) + elsif val > node.val + node.right = insert_helper(node.right, val) + else + # 重複ノードは挿入せず、そのまま返す + return node + end + # ノードの高さを更新する + update_height(node) + # 2. 回転操作を行い、部分木の平衡を回復する + rotate(node) + end + + ### ノードを削除 ### + def remove(val) + @root = remove_helper(@root, val) + end + + # ## ノードを再帰的に削除(補助メソッド)### + def remove_helper(node, val) + return if node.nil? + # 1. ノードを探索して削除 + if val < node.val + node.left = remove_helper(node.left, val) + elsif val > node.val + node.right = remove_helper(node.right, val) + else + if node.left.nil? || node.right.nil? + child = node.left || node.right + # 子ノード数 = 0 の場合、node をそのまま削除して返す + return if child.nil? + # 子ノード数 = 1 の場合、node をそのまま削除する + node = child + else + # 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える + temp = node.right + while !temp.left.nil? + temp = temp.left + end + node.right = remove_helper(node.right, temp.val) + node.val = temp.val + end + end + # ノードの高さを更新する + update_height(node) + # 2. 回転操作を行い、部分木の平衡を回復する + rotate(node) + end + + ### ノードを検索 ### + def search(val) + cur = @root + # ループで探索し、葉ノードを越えたら抜ける + while !cur.nil? + # 目標ノードは cur の右部分木にある + if cur.val < val + cur = cur.right + # 目標ノードは cur の左部分木にある + elsif cur.val > val + cur = cur.left + # 目標ノードが見つかったらループを抜ける + else + break + end + end + # 目標ノードを返す + cur + end +end + +### Driver Code ### +if __FILE__ == $0 + def test_insert(tree, val) + tree.insert(val) + puts "\nノード #{val} を挿入した後、AVL 木は" + print_tree(tree.get_root) + end + + def test_remove(tree, val) + tree.remove(val) + puts "\nノード #{val} を削除した後、AVL 木は" + print_tree(tree.get_root) + end + + # 空の AVL 木を初期化する + avl_tree = AVLTree.new + + # ノードを挿入する + # ノード挿入後に AVL 木がどのように平衡を保つかに注目 + for val in [1, 2, 3, 4, 5, 8, 7, 9, 10, 6] + test_insert(avl_tree, val) + end + + # 重複ノードを挿入する + test_insert(avl_tree, 7) + + # ノードを削除する + # ノード削除後に AVL 木がどのように平衡を保つかに注目 + test_remove(avl_tree, 8) # 次数 0 のノードを削除する + test_remove(avl_tree, 5) # 次数 1 のノードを削除する + test_remove(avl_tree, 4) # 次数 2 のノードを削除する + + result_node = avl_tree.search(7) + puts "\n見つかったノードオブジェクトは #{result_node}、ノードの値 = #{result_node.val}" +end diff --git a/ja/codes/ruby/chapter_tree/binary_search_tree.rb b/ja/codes/ruby/chapter_tree/binary_search_tree.rb new file mode 100644 index 000000000..9035264da --- /dev/null +++ b/ja/codes/ruby/chapter_tree/binary_search_tree.rb @@ -0,0 +1,161 @@ +=begin +File: binary_search_tree.rb +Created Time: 2024-04-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### 二分探索木 ### +class BinarySearchTree + ### コンストラクタ ### + def initialize + # 空の木を初期化する + @root = nil + end + + ### 二分木の根ノードを取得 ### + def get_root + @root + end + + ### ノードを検索 ### + def search(num) + cur = @root + + # ループで探索し、葉ノードを越えたら抜ける + while !cur.nil? + # 目標ノードは cur の右部分木にある + if cur.val < num + cur = cur.right + # 目標ノードは cur の左部分木にある + elsif cur.val > num + cur = cur.left + # 目標ノードが見つかったらループを抜ける + else + break + end + end + + cur + end + + ### ノードを挿入 ### + def insert(num) + # 木が空なら、根ノードを初期化する + if @root.nil? + @root = TreeNode.new(num) + return + end + + # ループで探索し、葉ノードを越えたら抜ける + cur, pre = @root, nil + while !cur.nil? + # 重複ノードが見つかったら、直ちに返す + return if cur.val == num + + pre = cur + # 挿入位置は cur の右部分木にある + if cur.val < num + cur = cur.right + # 挿入位置は cur の左部分木にある + else + cur = cur.left + end + end + + # ノードを挿入 + node = TreeNode.new(num) + if pre.val < num + pre.right = node + else + pre.left = node + end + end + + ### ノードを削除 ### + def remove(num) + # 木が空なら、そのまま早期リターンする + return if @root.nil? + + # ループで探索し、葉ノードを越えたら抜ける + cur, pre = @root, nil + while !cur.nil? + # 削除対象のノードが見つかったら、ループを抜ける + break if cur.val == num + + pre = cur + # 削除対象ノードは cur の右部分木にある + if cur.val < num + cur = cur.right + # 削除対象ノードは cur の左部分木にある + else + cur = cur.left + end + end + # 削除対象ノードがなければそのまま返す + return if cur.nil? + + # 子ノード数 = 0 or 1 + if cur.left.nil? || cur.right.nil? + # 子ノード数が 0 / 1 のとき、child = null / その子ノード + child = cur.left || cur.right + # ノード cur を削除する + if cur != @root + if pre.left == cur + pre.left = child + else + pre.right = child + end + else + # 削除ノードが根ノードなら、根ノードを再設定 + @root = child + end + # 子ノード数 = 2 + else + # 中順走査における cur の次ノードを取得 + tmp = cur.right + while !tmp.left.nil? + tmp = tmp.left + end + # ノード tmp を再帰的に削除 + remove(tmp.val) + # tmp で cur を上書きする + cur.val = tmp.val + end + end +end + +### Driver Code ### +if __FILE__ == $0 + # 二分探索木を初期化 + bst = BinarySearchTree.new + nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] + # 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる + nums.each { |num| bst.insert(num) } + puts "\n初期化された二分木は\n" + print_tree(bst.get_root) + + # ノードを探索 + node = bst.search(7) + puts "\n見つかったノードオブジェクトは: #{node}、ノードの値 = #{node.val}" + + # ノードを挿入 + bst.insert(16) + puts "\nノード 16 を挿入した後、二分木は\n" + print_tree(bst.get_root) + + # ノードを削除 + bst.remove(1) + puts "\nノード 1 を削除した後、二分木は\n" + print_tree(bst.get_root) + + bst.remove(2) + puts "\nノード 2 を削除した後、二分木は\n" + print_tree(bst.get_root) + + bst.remove(4) + puts "\nノード 4 を削除した後、二分木は\n" + print_tree(bst.get_root) +end diff --git a/ja/codes/ruby/chapter_tree/binary_tree.rb b/ja/codes/ruby/chapter_tree/binary_tree.rb new file mode 100644 index 000000000..894a4ee0b --- /dev/null +++ b/ja/codes/ruby/chapter_tree/binary_tree.rb @@ -0,0 +1,38 @@ +=begin +File: binary_tree.rb +Created Time: 2024-04-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### Driver Code ### +if __FILE__ == $0 + # 二分木を初期化する + # ノードを初期化する + n1 = TreeNode.new(1) + n2 = TreeNode.new(2) + n3 = TreeNode.new(3) + n4 = TreeNode.new(4) + n5 = TreeNode.new(5) + # ノード間の参照(ポインタ)を構築する + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + puts "\n二分木を初期化\n\n" + print_tree(n1) + + # ノードの挿入と削除 + _p = TreeNode.new(0) + # n1 -> n2 の間にノード _p を挿入する + n1.left = _p + _p.left = n2 + puts "\nノード _p を挿入した後\n\n" + print_tree(n1) + # ノードを削除 + n1.left = n2 + puts "\nノード _p を削除した後\n\n" + print_tree(n1) +end diff --git a/ja/codes/ruby/chapter_tree/binary_tree_bfs.rb b/ja/codes/ruby/chapter_tree/binary_tree_bfs.rb new file mode 100644 index 000000000..f1263ccfe --- /dev/null +++ b/ja/codes/ruby/chapter_tree/binary_tree_bfs.rb @@ -0,0 +1,36 @@ +=begin +File: binary_tree_bfs.rb +Created Time: 2024-04-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative '../utils/tree_node' +require_relative '../utils/print_util' + +### レベル順走査 ### +def level_order(root) + # キューを初期化し、ルートノードを追加する + queue = [root] + # 走査順序を保存するためのリストを初期化する + res = [] + while !queue.empty? + node = queue.shift # デキュー + res << node.val # ノードの値を保存する + queue << node.left unless node.left.nil? # 左子ノードをキューに追加 + queue << node.right unless node.right.nil? # 右子ノードをキューに追加 + end + res +end + +### Driver Code ### +if __FILE__ == $0 + # 二分木を初期化 + # ここでは、配列から直接二分木を生成する関数を利用する + root = arr_to_tree([1, 2, 3, 4, 5, 6, 7]) + puts "\n二分木を初期化\n\n" + print_tree(root) + + # レベル順走査 + res = level_order(root) + puts "\nレベル順走査のノード出力順 = #{res}" +end diff --git a/ja/codes/ruby/chapter_tree/binary_tree_dfs.rb b/ja/codes/ruby/chapter_tree/binary_tree_dfs.rb new file mode 100644 index 000000000..b707bdc75 --- /dev/null +++ b/ja/codes/ruby/chapter_tree/binary_tree_dfs.rb @@ -0,0 +1,62 @@ +=begin +File: binary_tree_dfs.rb +Created Time: 2024-04-18 +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.nil? + + # 訪問順序:根ノード -> 左部分木 -> 右部分木 + $res << root.val + pre_order(root.left) + pre_order(root.right) +end + +### 中順走査 ### +def in_order(root) + return if root.nil? + + # 訪問優先順: 左部分木 -> 根ノード -> 右部分木 + in_order(root.left) + $res << root.val + in_order(root.right) +end + +### 後順走査 ### +def post_order(root) + return if root.nil? + + # 訪問優先順: 左部分木 -> 右部分木 -> 根ノード + post_order(root.left) + post_order(root.right) + $res << root.val +end + +### Driver Code ### +if __FILE__ == $0 + # 二分木を初期化 + # ここでは、配列から直接二分木を生成する関数を利用する + root = arr_to_tree([1, 2, 3, 4, 5, 6, 7]) + puts "\n二分木を初期化\n\n" + print_tree(root) + + # 先行順走査 + $res = [] + pre_order(root) + puts "\n前順走査のノード出力順 = #{$res}" + + # 中順走査 + $res.clear + in_order(root) + puts "\nn中順走査のノード出力順 = #{$res}" + + # 後順走査 + $res.clear + post_order(root) + puts "\nn後順走査のノード出力順 = #{$res}" +end diff --git a/ja/codes/ruby/test_all.rb b/ja/codes/ruby/test_all.rb new file mode 100644 index 000000000..a4d417800 --- /dev/null +++ b/ja/codes/ruby/test_all.rb @@ -0,0 +1,23 @@ +require 'open3' + +start_time = Time.now +ruby_code_dir = File.dirname(__FILE__) +files = Dir.glob("#{ruby_code_dir}/chapter_*/*.rb") + +errors = [] + +files.each do |file| + stdout, stderr, status = Open3.capture3("ruby #{file}") + errors << stderr unless status.success? +end + +puts "\x1b[34mTested #{files.count} files\x1b[m" + +unless errors.empty? + puts "\x1b[33mFound exception in #{errors.length} files\x1b[m" + raise errors.join("\n\n") +else + puts "\x1b[32mPASS\x1b[m" +end + +puts "Testing finishes after #{((Time.now - start_time) * 1000).round} ms" diff --git a/ja/codes/ruby/utils/list_node.rb b/ja/codes/ruby/utils/list_node.rb new file mode 100644 index 000000000..0a465345c --- /dev/null +++ b/ja/codes/ruby/utils/list_node.rb @@ -0,0 +1,38 @@ +=begin +File: list_node.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 連結リストノードクラス ### +class ListNode + attr_accessor :val # ノード値 + attr_accessor :next # 次のノードへの参照 + + def initialize(val=0, next_node=nil) + @val = val + @next = next_node + end +end + +### リストを連結リストにデシリアライズ ### +def arr_to_linked_list(arr) + head = current = ListNode.new(arr[0]) + + for i in 1...arr.length + current.next = ListNode.new(arr[i]) + current = current.next + end + + head +end + +### 連結リストをリストにシリアライズ ### +def linked_list_to_arr(head) + arr = [] + + while head + arr << head.val + head = head.next + end +end diff --git a/ja/codes/ruby/utils/print_util.rb b/ja/codes/ruby/utils/print_util.rb new file mode 100644 index 000000000..df9a05c3a --- /dev/null +++ b/ja/codes/ruby/utils/print_util.rb @@ -0,0 +1,80 @@ +=begin +File: print_util.rb +Created Time: 2024-03-18 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +require_relative "./tree_node" + +### 行列を出力 ### +def print_matrix(mat) + s = [] + mat.each { |arr| s << " #{arr.to_s}" } + puts "[\n#{s.join(",\n")}\n]" +end + +### 連結リストを出力 ### +def print_linked_list(head) + list = [] + while head + list << head.val + head = head.next + end + puts "#{list.join(" -> ")}" +end + +class Trunk + attr_accessor :prev, :str + + def initialize(prev, str) + @prev = prev + @str = str + end +end + +def show_trunk(p) + return if p.nil? + + show_trunk(p.prev) + print p.str +end + +### 二分木を出力 ### +# This tree printer is borrowed from TECHIE DELIGHT +# https://www.techiedelight.com/c-program-print-binary-tree/ +def print_tree(root, prev=nil, is_right=false) + return if root.nil? + + prev_str = " " + trunk = Trunk.new(prev, prev_str) + print_tree(root.right, trunk, true) + + if prev.nil? + trunk.str = "———" + elsif is_right + trunk.str = "/———" + prev_str = " |" + else + trunk.str = "\\———" + prev.str = prev_str + end + + show_trunk(trunk) + puts " #{root.val}" + prev.str = prev_str if prev + trunk.str = " |" + print_tree(root.left, trunk, false) +end + +### ハッシュテーブルを出力 ### +def print_hash_map(hmap) + hmap.entries.each { |key, value| puts "#{key} -> #{value}" } +end + +### ヒープを出力 ### +def print_heap(heap) + puts "ヒープの配列表現:#{heap}" + puts "ヒープの木構造表現:" + root = arr_to_tree(heap) + print_tree(root) +end diff --git a/ja/codes/ruby/utils/tree_node.rb b/ja/codes/ruby/utils/tree_node.rb new file mode 100644 index 000000000..30e7bf8af --- /dev/null +++ b/ja/codes/ruby/utils/tree_node.rb @@ -0,0 +1,53 @@ +=begin +File: tree_node.rb +Created Time: 2024-03-30 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 二分木ノードクラス ### +class TreeNode + attr_accessor :val # ノード値 + attr_accessor :height # ノードの高さ + attr_accessor :left # 左子ノードへの参照 + attr_accessor :right # 右子ノードへの参照 + + def initialize(val=0) + @val = val + @height = 0 + end +end + +### リストを二分木にデシリアライズ:再帰 ### +def arr_to_tree_dfs(arr, i) + # 添字が配列長を超えるか、対応する要素が nil なら、nil を返す + return if i < 0 || i >= arr.length || arr[i].nil? + # 現在のノードを構築する + root = TreeNode.new(arr[i]) + # 左右の部分木を再帰的に構築する + root.left = arr_to_tree_dfs(arr, 2 * i + 1) + root.right = arr_to_tree_dfs(arr, 2 * i + 2) + root +end + +### リストを二分木にデシリアライズ ### +def arr_to_tree(arr) + arr_to_tree_dfs(arr, 0) +end + +### 二分木をリストにシリアライズ:再帰 ### +def tree_to_arr_dfs(root, i, res) + return if root.nil? + + res += Array.new(i - res.length + 1) if i >= res.length + res[i] = root.val + + tree_to_arr_dfs(root.left, 2 * i + 1, res) + tree_to_arr_dfs(root.right, 2 * i + 2, res) +end + +### 二分木をリストにシリアライズ ### +def tree_to_arr(root) + res = [] + tree_to_arr_dfs(root, 0, res) + res +end diff --git a/ja/codes/ruby/utils/vertex.rb b/ja/codes/ruby/utils/vertex.rb new file mode 100644 index 000000000..6a99e3b5a --- /dev/null +++ b/ja/codes/ruby/utils/vertex.rb @@ -0,0 +1,24 @@ +=begin +File: vertex.rb +Created Time: 2024-04-25 +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com) +=end + +### 頂点クラス ### +class Vertex + attr_accessor :val + + def initialize(val) + @val = val + end +end + +### 値リスト vals を入力し、頂点リスト vets を返す ### +def vals_to_vets(vals) + Array.new(vals.length) { |i| Vertex.new(vals[i]) } +end + +### 頂点リスト vets を入力し、値リスト vals を返す ### +def vets_to_vals(vets) + Array.new(vets.length) { |i| vets[i].val } +end diff --git a/ja/codes/rust/.gitignore b/ja/codes/rust/.gitignore new file mode 100644 index 000000000..447098846 --- /dev/null +++ b/ja/codes/rust/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock \ No newline at end of file diff --git a/ja/codes/rust/Cargo.toml b/ja/codes/rust/Cargo.toml new file mode 100644 index 000000000..160f9134c --- /dev/null +++ b/ja/codes/rust/Cargo.toml @@ -0,0 +1,413 @@ +[package] +name = "hello-algo-rust" +version = "0.1.0" +edition = "2021" +publish = false + +# Run Command: cargo run --bin time_complexity +[[bin]] +name = "time_complexity" +path = "chapter_computational_complexity/time_complexity.rs" + +# Run Command: cargo run --bin worst_best_time_complexity +[[bin]] +name = "worst_best_time_complexity" +path = "chapter_computational_complexity/worst_best_time_complexity.rs" + +# Run Command: cargo run --bin space_complexity +[[bin]] +name = "space_complexity" +path = "chapter_computational_complexity/space_complexity.rs" + +# Run Command: cargo run --bin iteration +[[bin]] +name = "iteration" +path = "chapter_computational_complexity/iteration.rs" + +# Run Command: cargo run --bin recursion +[[bin]] +name = "recursion" +path = "chapter_computational_complexity/recursion.rs" + +# Run Command: cargo run --bin two_sum +[[bin]] +name = "two_sum" +path = "chapter_searching/two_sum.rs" + +# Run Command: cargo run --bin array +[[bin]] +name = "array" +path = "chapter_array_and_linkedlist/array.rs" + +# Run Command: cargo run --bin linked_list +[[bin]] +name = "linked_list" +path = "chapter_array_and_linkedlist/linked_list.rs" + +# Run Command: cargo run --bin list +[[bin]] +name = "list" +path = "chapter_array_and_linkedlist/list.rs" + +# Run Command: cargo run --bin my_list +[[bin]] +name = "my_list" +path = "chapter_array_and_linkedlist/my_list.rs" + +# Run Command: cargo run --bin stack +[[bin]] +name = "stack" +path = "chapter_stack_and_queue/stack.rs" + +# Run Command: cargo run --bin linkedlist_stack +[[bin]] +name = "linkedlist_stack" +path = "chapter_stack_and_queue/linkedlist_stack.rs" + +# Run Command: cargo run --bin queue +[[bin]] +name = "queue" +path = "chapter_stack_and_queue/queue.rs" + +# Run Command: cargo run --bin linkedlist_queue +[[bin]] +name = "linkedlist_queue" +path = "chapter_stack_and_queue/linkedlist_queue.rs" + +# Run Command: cargo run --bin deque +[[bin]] +name = "deque" +path = "chapter_stack_and_queue/deque.rs" + +# Run Command: cargo run --bin array_deque +[[bin]] +name = "array_deque" +path = "chapter_stack_and_queue/array_deque.rs" + +# Run Command: cargo run --bin linkedlist_deque +[[bin]] +name = "linkedlist_deque" +path = "chapter_stack_and_queue/linkedlist_deque.rs" + +# Run Command: cargo run --bin simple_hash +[[bin]] +name = "simple_hash" +path = "chapter_hashing/simple_hash.rs" + +# Run Command: cargo run --bin hash_map +[[bin]] +name = "hash_map" +path = "chapter_hashing/hash_map.rs" + +# Run Command: cargo run --bin array_hash_map +[[bin]] +name = "array_hash_map" +path = "chapter_hashing/array_hash_map.rs" + +# Run Command: cargo run --bin build_in_hash +[[bin]] +name = "build_in_hash" +path = "chapter_hashing/build_in_hash.rs" + +# Run Command: cargo run --bin hash_map_chaining +[[bin]] +name = "hash_map_chaining" +path = "chapter_hashing/hash_map_chaining.rs" + +# Run Command: cargo run --bin hash_map_open_addressing +[[bin]] +name = "hash_map_open_addressing" +path = "chapter_hashing/hash_map_open_addressing.rs" + +# Run Command: cargo run --bin binary_search +[[bin]] +name = "binary_search" +path = "chapter_searching/binary_search.rs" + +# Run Command: cargo run --bin binary_search_edge +[[bin]] +name = "binary_search_edge" +path = "chapter_searching/binary_search_edge.rs" + +# Run Command: cargo run --bin binary_search_insertion +[[bin]] +name = "binary_search_insertion" +path = "chapter_searching/binary_search_insertion.rs" + +# Run Command: cargo run --bin bubble_sort +[[bin]] +name = "bubble_sort" +path = "chapter_sorting/bubble_sort.rs" + +# Run Command: cargo run --bin insertion_sort +[[bin]] +name = "insertion_sort" +path = "chapter_sorting/insertion_sort.rs" + +# Run Command: cargo run --bin quick_sort +[[bin]] +name = "quick_sort" +path = "chapter_sorting/quick_sort.rs" + +# Run Command: cargo run --bin merge_sort +[[bin]] +name = "merge_sort" +path = "chapter_sorting/merge_sort.rs" + +# Run Command: cargo run --bin selection_sort +[[bin]] +name = "selection_sort" +path = "chapter_sorting/selection_sort.rs" + +# Run Command: cargo run --bin bucket_sort +[[bin]] +name = "bucket_sort" +path = "chapter_sorting/bucket_sort.rs" + +# Run Command: cargo run --bin heap_sort +[[bin]] +name = "heap_sort" +path = "chapter_sorting/heap_sort.rs" + +# Run Command: cargo run --bin counting_sort +[[bin]] +name = "counting_sort" +path = "chapter_sorting/counting_sort.rs" + +# Run Command: cargo run --bin radix_sort +[[bin]] +name = "radix_sort" +path = "chapter_sorting/radix_sort.rs" + +# Run Command: cargo run --bin array_stack +[[bin]] +name = "array_stack" +path = "chapter_stack_and_queue/array_stack.rs" + +# Run Command: cargo run --bin array_queue +[[bin]] +name = "array_queue" +path = "chapter_stack_and_queue/array_queue.rs" + +# Run Command: cargo run --bin array_binary_tree +[[bin]] +name = "array_binary_tree" +path = "chapter_tree/array_binary_tree.rs" + +# Run Command: cargo run --bin avl_tree +[[bin]] +name = "avl_tree" +path = "chapter_tree/avl_tree.rs" + +# Run Command: cargo run --bin binary_search_tree +[[bin]] +name = "binary_search_tree" +path = "chapter_tree/binary_search_tree.rs" + +# Run Command: cargo run --bin binary_tree_bfs +[[bin]] +name = "binary_tree_bfs" +path = "chapter_tree/binary_tree_bfs.rs" + +# Run Command: cargo run --bin binary_tree_dfs +[[bin]] +name = "binary_tree_dfs" +path = "chapter_tree/binary_tree_dfs.rs" + +# Run Command: cargo run --bin binary_tree +[[bin]] +name = "binary_tree" +path = "chapter_tree/binary_tree.rs" + +# Run Command: cargo run --bin heap +[[bin]] +name = "heap" +path = "chapter_heap/heap.rs" + +# Run Command: cargo run --bin my_heap +[[bin]] +name = "my_heap" +path = "chapter_heap/my_heap.rs" + +# Run Command: cargo run --bin top_k +[[bin]] +name = "top_k" +path = "chapter_heap/top_k.rs" + +# Run Command: cargo run --bin graph_adjacency_list +[[bin]] +name = "graph_adjacency_list" +path = "chapter_graph/graph_adjacency_list.rs" + +# Run Command: cargo run --bin graph_adjacency_matrix +[[bin]] +name = "graph_adjacency_matrix" +path = "chapter_graph/graph_adjacency_matrix.rs" + +# Run Command: cargo run --bin graph_bfs +[[bin]] +name = "graph_bfs" +path = "chapter_graph/graph_bfs.rs" + +# Run Command: cargo run --bin graph_dfs +[[bin]] +name = "graph_dfs" +path = "chapter_graph/graph_dfs.rs" + +# Run Command: cargo run --bin linear_search +[[bin]] +name = "linear_search" +path = "chapter_searching/linear_search.rs" + +# Run Command: cargo run --bin hashing_search +[[bin]] +name = "hashing_search" +path = "chapter_searching/hashing_search.rs" + +# Run Command: cargo run --bin climbing_stairs_dfs +[[bin]] +name = "climbing_stairs_dfs" +path = "chapter_dynamic_programming/climbing_stairs_dfs.rs" + +# Run Command: cargo run --bin climbing_stairs_dfs_mem +[[bin]] +name = "climbing_stairs_dfs_mem" +path = "chapter_dynamic_programming/climbing_stairs_dfs_mem.rs" + +# Run Command: cargo run --bin climbing_stairs_dp +[[bin]] +name = "climbing_stairs_dp" +path = "chapter_dynamic_programming/climbing_stairs_dp.rs" + +# Run Command: cargo run --bin min_cost_climbing_stairs_dp +[[bin]] +name = "min_cost_climbing_stairs_dp" +path = "chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs" + +# Run Command: cargo run --bin climbing_stairs_constraint_dp +[[bin]] +name = "climbing_stairs_constraint_dp" +path = "chapter_dynamic_programming/climbing_stairs_constraint_dp.rs" + +# Run Command: cargo run --bin climbing_stairs_backtrack +[[bin]] +name = "climbing_stairs_backtrack" +path = "chapter_dynamic_programming/climbing_stairs_backtrack.rs" + +# Run Command: cargo run --bin subset_sum_i_naive +[[bin]] +name = "subset_sum_i_naive" +path = "chapter_backtracking/subset_sum_i_naive.rs" + +# Run Command: cargo run --bin subset_sum_i +[[bin]] +name = "subset_sum_i" +path = "chapter_backtracking/subset_sum_i.rs" + +# Run Command: cargo run --bin subset_sum_ii +[[bin]] +name = "subset_sum_ii" +path = "chapter_backtracking/subset_sum_ii.rs" + +# Run Command: cargo run --bin coin_change +[[bin]] +name = "coin_change" +path = "chapter_dynamic_programming/coin_change.rs" + +# Run Command: cargo run --bin coin_change_ii +[[bin]] +name = "coin_change_ii" +path = "chapter_dynamic_programming/coin_change_ii.rs" + +# Run Command: cargo run --bin unbounded_knapsack +[[bin]] +name = "unbounded_knapsack" +path = "chapter_dynamic_programming/unbounded_knapsack.rs" + +# Run Command: cargo run --bin knapsack +[[bin]] +name = "knapsack" +path = "chapter_dynamic_programming/knapsack.rs" + +# Run Command: cargo run --bin min_path_sum +[[bin]] +name = "min_path_sum" +path = "chapter_dynamic_programming/min_path_sum.rs" + +# Run Command: cargo run --bin edit_distance +[[bin]] +name = "edit_distance" +path = "chapter_dynamic_programming/edit_distance.rs" + +# Run Command: cargo run --bin n_queens +[[bin]] +name = "n_queens" +path = "chapter_backtracking/n_queens.rs" + +# Run Command: cargo run --bin permutations_i +[[bin]] +name = "permutations_i" +path = "chapter_backtracking/permutations_i.rs" + +# Run Command: cargo run --bin permutations_ii +[[bin]] +name = "permutations_ii" +path = "chapter_backtracking/permutations_ii.rs" + +# Run Command: cargo run --bin preorder_traversal_i_compact +[[bin]] +name = "preorder_traversal_i_compact" +path = "chapter_backtracking/preorder_traversal_i_compact.rs" + +# Run Command: cargo run --bin preorder_traversal_ii_compact +[[bin]] +name = "preorder_traversal_ii_compact" +path = "chapter_backtracking/preorder_traversal_ii_compact.rs" + +# Run Command: cargo run --bin preorder_traversal_iii_compact +[[bin]] +name = "preorder_traversal_iii_compact" +path = "chapter_backtracking/preorder_traversal_iii_compact.rs" + +# Run Command: cargo run --bin preorder_traversal_iii_template +[[bin]] +name = "preorder_traversal_iii_template" +path = "chapter_backtracking/preorder_traversal_iii_template.rs" + +# Run Command: cargo run --bin binary_search_recur +[[bin]] +name = "binary_search_recur" +path = "chapter_divide_and_conquer/binary_search_recur.rs" + +# Run Command: cargo run --bin hanota +[[bin]] +name = "hanota" +path = "chapter_divide_and_conquer/hanota.rs" + +# Run Command: cargo run --bin build_tree +[[bin]] +name = "build_tree" +path = "chapter_divide_and_conquer/build_tree.rs" + +# Run Command: cargo run --bin coin_change_greedy +[[bin]] +name = "coin_change_greedy" +path = "chapter_greedy/coin_change_greedy.rs" + +# Run Command: cargo run --bin fractional_knapsack +[[bin]] +name = "fractional_knapsack" +path = "chapter_greedy/fractional_knapsack.rs" + +# Run Command: cargo run --bin max_capacity +[[bin]] +name = "max_capacity" +path = "chapter_greedy/max_capacity.rs" + +# Run Command: cargo run --bin max_product_cutting +[[bin]] +name = "max_product_cutting" +path = "chapter_greedy/max_product_cutting.rs" + +[dependencies] +rand = "0.8.5" diff --git a/ja/codes/rust/chapter_array_and_linkedlist/array.rs b/ja/codes/rust/chapter_array_and_linkedlist/array.rs new file mode 100644 index 000000000..f2a1ce1c5 --- /dev/null +++ b/ja/codes/rust/chapter_array_and_linkedlist/array.rs @@ -0,0 +1,111 @@ +/* + * File: array.rs + * Created Time: 2023-01-15 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; +use rand::Rng; + +/* 要素へランダムアクセス */ +fn random_access(nums: &[i32]) -> i32 { + // 区間 [0, nums.len()) からランダムに数字を 1 つ選ぶ + let random_index = rand::thread_rng().gen_range(0..nums.len()); + // ランダムな要素を取得して返す + let random_num = nums[random_index]; + random_num +} + +/* 配列長を拡張する */ +fn extend(nums: &[i32], enlarge: usize) -> Vec { + // 拡張後の長さを持つ配列を初期化する + let mut res: Vec = vec![0; nums.len() + enlarge]; + // 元の配列の全要素を新しい配列にコピー + res[0..nums.len()].copy_from_slice(nums); + + // 拡張後の新しい配列を返す + res +} + +/* 配列の index 番目に要素 num を挿入 */ +fn insert(nums: &mut [i32], num: i32, index: usize) { + // インデックス index 以降の全要素を 1 つ後ろへ移動する + for i in (index + 1..nums.len()).rev() { + nums[i] = nums[i - 1]; + } + // index の要素に num を代入する + nums[index] = num; +} + +/* index の要素を削除する */ +fn remove(nums: &mut [i32], index: usize) { + // インデックス index より後ろの全要素を 1 つ前へ移動する + for i in index..nums.len() - 1 { + nums[i] = nums[i + 1]; + } +} + +/* 配列を走査 */ +fn traverse(nums: &[i32]) { + let mut _count = 0; + // インデックスで配列を走査 + for i in 0..nums.len() { + _count += nums[i]; + } + // 配列要素を直接走査 + _count = 0; + for &num in nums { + _count += num; + } +} + +/* 配列内で指定要素を探す */ +fn find(nums: &[i32], target: i32) -> Option { + for i in 0..nums.len() { + if nums[i] == target { + return Some(i); + } + } + None +} + +/* Driver Code */ +fn main() { + /* 配列を初期化 */ + let arr: [i32; 5] = [0; 5]; + print!("配列 arr = "); + print_util::print_array(&arr); + // Rust では、長さを指定する場合([i32; 5])は配列、指定しない場合(&[i32])はスライスである + // Rust の配列はコンパイル時に長さが確定するよう設計されているため、長さには定数しか使えない + // Vector は Rust で通常動的配列として使われる型である + // extend() メソッドを実装しやすくするため、以下では vector を配列(array)として扱う + let nums: Vec = vec![1, 3, 2, 5, 4]; + print!("\n配列 nums = "); + print_util::print_array(&nums); + + // ランダムアクセス + let random_num = random_access(&nums); + println!("\nnums からランダムな要素 {} を取得", random_num); + + // 長さを拡張 + let mut nums: Vec = extend(&nums, 3); + print!("配列の長さを 8 に拡張すると、nums = "); + print_util::print_array(&nums); + + // 要素を挿入する + insert(&mut nums, 6, 3); + print!("\nインデックス 3 に数値 6 を挿入すると、nums = "); + print_util::print_array(&nums); + + // 要素を削除 + remove(&mut nums, 2); + print!("\nインデックス 2 の要素を削除すると、nums = "); + print_util::print_array(&nums); + + // 配列を走査 + traverse(&nums); + + // 要素を探索する + let index = find(&nums, 3).unwrap(); + println!("\nnums 内で要素 3 を検索すると、インデックス = {}", index); +} diff --git a/ja/codes/rust/chapter_array_and_linkedlist/linked_list.rs b/ja/codes/rust/chapter_array_and_linkedlist/linked_list.rs new file mode 100644 index 000000000..641cfec11 --- /dev/null +++ b/ja/codes/rust/chapter_array_and_linkedlist/linked_list.rs @@ -0,0 +1,100 @@ +/* + * File: linked_list.rs + * Created Time: 2023-03-05 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, ListNode}; +use std::cell::RefCell; +use std::rc::Rc; + +/* 連結リストでノード n0 の後ろにノード P を挿入する */ +#[allow(non_snake_case)] +pub fn insert(n0: &Rc>>, P: Rc>>) { + let n1 = n0.borrow_mut().next.take(); + P.borrow_mut().next = n1; + n0.borrow_mut().next = Some(P); +} + +/* 連結リストでノード n0 の直後のノードを削除する */ +#[allow(non_snake_case)] +pub fn remove(n0: &Rc>>) { + // n0 -> P -> n1 + let P = n0.borrow_mut().next.take(); + if let Some(node) = P { + let n1 = node.borrow_mut().next.take(); + n0.borrow_mut().next = n1; + } +} + +/* 連結リスト内で index 番目のノードにアクセス */ +pub fn access(head: Rc>>, index: i32) -> Option>>> { + fn dfs( + head: Option<&Rc>>>, + index: i32, + ) -> Option>>> { + if index <= 0 { + return head.cloned(); + } + + if let Some(node) = head { + dfs(node.borrow().next.as_ref(), index - 1) + } else { + None + } + } + + dfs(Some(head).as_ref(), index) +} + +/* 連結リストで値が target の最初のノードを探す */ +pub fn find(head: Rc>>, target: T) -> i32 { + fn find(head: Option<&Rc>>>, target: T, idx: i32) -> i32 { + if let Some(node) = head { + if node.borrow().val == target { + return idx; + } + return find(node.borrow().next.as_ref(), target, idx + 1); + } else { + -1 + } + } + + find(Some(head).as_ref(), target, 0) +} + +/* Driver Code */ +fn main() { + /* 連結リストを初期化 */ + // 各ノードを初期化 + let n0 = ListNode::new(1); + let n1 = ListNode::new(3); + let n2 = ListNode::new(2); + let n3 = ListNode::new(5); + let n4 = ListNode::new(4); + // ノード間の参照を構築する + n0.borrow_mut().next = Some(n1.clone()); + n1.borrow_mut().next = Some(n2.clone()); + n2.borrow_mut().next = Some(n3.clone()); + n3.borrow_mut().next = Some(n4.clone()); + print!("初期化された連結リストは "); + print_util::print_linked_list(&n0); + + /* ノードを挿入 */ + insert(&n0, ListNode::new(0)); + print!("ノード挿入後の連結リストは "); + print_util::print_linked_list(&n0); + + /* ノードを削除 */ + remove(&n0); + print!("ノード削除後の連結リストは "); + print_util::print_linked_list(&n0); + + /* ノードにアクセス */ + let node = access(n0.clone(), 3); + println!("連結リストのインデックス 3 にあるノードの値 = {}", node.unwrap().borrow().val); + + /* ノードを探索 */ + let index = find(n0.clone(), 2); + println!("連結リスト内で値が 2 のノードのインデックス = {}", index); +} diff --git a/ja/codes/rust/chapter_array_and_linkedlist/list.rs b/ja/codes/rust/chapter_array_and_linkedlist/list.rs new file mode 100644 index 000000000..0047f684a --- /dev/null +++ b/ja/codes/rust/chapter_array_and_linkedlist/list.rs @@ -0,0 +1,71 @@ +/* + * File: list.rs + * Created Time: 2023-01-18 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ +use hello_algo_rust::include::print_util; + +/* Driver Code */ +fn main() { + // リストを初期化 + let mut nums: Vec = vec![1, 3, 2, 5, 4]; + print!("リスト nums = "); + print_util::print_array(&nums); + + // 要素にアクセス + let num = nums[1]; + println!("\nインデックス 1 の要素にアクセスすると、num = {num}"); + + // 要素を更新 + nums[1] = 0; + print!("インデックス 1 の要素を 0 に更新すると、nums = "); + print_util::print_array(&nums); + + // リストを空にする + nums.clear(); + print!("\nリストを空にした後、nums = "); + print_util::print_array(&nums); + + // 末尾に要素を追加 + nums.push(1); + nums.push(3); + nums.push(2); + nums.push(5); + nums.push(4); + print!("\n要素を追加した後、nums = "); + print_util::print_array(&nums); + + // 中間に要素を挿入 + nums.insert(3, 6); + print!("\nインデックス 3 に数値 6 を挿入すると、nums = "); + print_util::print_array(&nums); + + // 要素を削除 + nums.remove(3); + print!("\nインデックス 3 の要素を削除すると、nums = "); + print_util::print_array(&nums); + + // インデックスでリストを走査 + let mut _count = 0; + for i in 0..nums.len() { + _count += nums[i]; + } + // リスト要素を直接走査 + _count = 0; + for x in &nums { + _count += x; + } + + // 2 つのリストを連結する + let mut nums1 = vec![6, 8, 7, 10, 9]; + nums.append(&mut nums1); // append(move)の後では nums1 は空になる! + + // nums.extend(&nums1); // extend(借用)の後も nums1 は引き続き使える + print!("\nリスト nums1 を nums の後ろに連結すると、nums = "); + print_util::print_array(&nums); + + // リストをソート + nums.sort(); + print!("\nリストをソートした後、nums = "); + print_util::print_array(&nums); +} diff --git a/ja/codes/rust/chapter_array_and_linkedlist/my_list.rs b/ja/codes/rust/chapter_array_and_linkedlist/my_list.rs new file mode 100644 index 000000000..914c3cb63 --- /dev/null +++ b/ja/codes/rust/chapter_array_and_linkedlist/my_list.rs @@ -0,0 +1,164 @@ +/* + * File: my_list.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +/* リストクラス */ +#[allow(dead_code)] +struct MyList { + arr: Vec, // 配列(リスト要素を格納) + capacity: usize, // リスト容量 + size: usize, // リストの長さ(現在の要素数) + extend_ratio: usize, // リスト拡張時の増加倍率 +} + +#[allow(unused, unused_comparisons)] +impl MyList { + /* コンストラクタ */ + pub fn new(capacity: usize) -> Self { + let mut vec = vec![0; capacity]; + Self { + arr: vec, + capacity, + size: 0, + extend_ratio: 2, + } + } + + /* リストの長さを取得(現在の要素数) */ + pub fn size(&self) -> usize { + return self.size; + } + + /* リスト容量を取得する */ + pub fn capacity(&self) -> usize { + return self.capacity; + } + + /* 要素にアクセス */ + pub fn get(&self, index: usize) -> i32 { + // インデックスが範囲外なら例外を送出する。以下同様 + if index >= self.size { + panic!("インデックスが範囲外です") + }; + return self.arr[index]; + } + + /* 要素を更新 */ + pub fn set(&mut self, index: usize, num: i32) { + if index >= self.size { + panic!("インデックスが範囲外です") + }; + self.arr[index] = num; + } + + /* 末尾に要素を追加 */ + pub fn add(&mut self, num: i32) { + // 要素数が容量を超えると、拡張機構が発動する + if self.size == self.capacity() { + self.extend_capacity(); + } + self.arr[self.size] = num; + // 要素数を更新 + self.size += 1; + } + + /* 中間に要素を挿入 */ + pub fn insert(&mut self, index: usize, num: i32) { + if index >= self.size() { + panic!("インデックスが範囲外です") + }; + // 要素数が容量を超えると、拡張機構が発動する + if self.size == self.capacity() { + self.extend_capacity(); + } + // index 以降の要素をすべて 1 つ後ろへずらす + for j in (index..self.size).rev() { + self.arr[j + 1] = self.arr[j]; + } + self.arr[index] = num; + // 要素数を更新 + self.size += 1; + } + + /* 要素を削除 */ + pub fn remove(&mut self, index: usize) -> i32 { + if index >= self.size() { + panic!("インデックスが範囲外です") + }; + let num = self.arr[index]; + // インデックス index より後の要素をすべて 1 つ前に移動する + for j in index..self.size - 1 { + self.arr[j] = self.arr[j + 1]; + } + // 要素数を更新 + self.size -= 1; + // 削除された要素を返す + return num; + } + + /* リストの拡張 */ + pub fn extend_capacity(&mut self) { + // 元の配列の extend_ratio 倍の長さを持つ新しい配列を作成し、元の配列をコピーする + let new_capacity = self.capacity * self.extend_ratio; + self.arr.resize(new_capacity, 0); + // リストの容量を更新 + self.capacity = new_capacity; + } + + /* リストを配列に変換する */ + pub fn to_array(&self) -> Vec { + // 有効長の範囲内のリスト要素のみを変換 + let mut arr = Vec::new(); + for i in 0..self.size { + arr.push(self.get(i)); + } + arr + } +} + +/* Driver Code */ +fn main() { + /* リストを初期化 */ + let mut nums = MyList::new(10); + /* 末尾に要素を追加 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + print!("リスト nums = "); + print_util::print_array(&nums.to_array()); + print!(" 、容量 = {} 、長さ = {}", nums.capacity(), nums.size()); + + /* 中間に要素を挿入 */ + nums.insert(3, 6); + print!("\nインデックス 3 に数値 6 を挿入すると、nums = "); + print_util::print_array(&nums.to_array()); + + /* 要素を削除 */ + nums.remove(3); + print!("\nインデックス 3 の要素を削除すると、nums = "); + print_util::print_array(&nums.to_array()); + + /* 要素にアクセス */ + let num = nums.get(1); + println!("\nインデックス 1 の要素にアクセスすると、num = {num}"); + + /* 要素を更新 */ + nums.set(1, 0); + print!("インデックス 1 の要素を 0 に更新すると、nums = "); + print_util::print_array(&nums.to_array()); + + /* 拡張機構をテストする */ + for i in 0..10 { + // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する + nums.add(i); + } + print!("\n拡張後のリスト nums = "); + print_util::print_array(&nums.to_array()); + print!(" 、容量 = {} 、長さ = {}", nums.capacity(), nums.size()); +} diff --git a/ja/codes/rust/chapter_backtracking/n_queens.rs b/ja/codes/rust/chapter_backtracking/n_queens.rs new file mode 100644 index 000000000..f5604eb0d --- /dev/null +++ b/ja/codes/rust/chapter_backtracking/n_queens.rs @@ -0,0 +1,76 @@ +/* + * File: n_queens.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +/* バックトラッキング:N クイーン */ +fn backtrack( + row: usize, + n: usize, + state: &mut Vec>, + res: &mut Vec>>, + cols: &mut [bool], + diags1: &mut [bool], + diags2: &mut [bool], +) { + // すべての行への配置が完了したら、解を記録する + if row == n { + res.push(state.clone()); + return; + } + // すべての列を走査 + for col in 0..n { + // このマスに対応する主対角線と副対角線を計算 + let diag1 = row + n - 1 - col; + let diag2 = row + col; + // 枝刈り:そのマスの列、主対角線、副対角線にクイーンがあってはならない + if !cols[col] && !diags1[diag1] && !diags2[diag2] { + // 試行:そのマスにクイーンを置く + state[row][col] = "Q".into(); + (cols[col], diags1[diag1], diags2[diag2]) = (true, true, true); + // 次の行に配置する + backtrack(row + 1, n, state, res, cols, diags1, diags2); + // 戻す:そのマスを空きマスに戻す + state[row][col] = "#".into(); + (cols[col], diags1[diag1], diags2[diag2]) = (false, false, false); + } + } +} + +/* N クイーンを解く */ +fn n_queens(n: usize) -> Vec>> { + // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す + let mut state: Vec> = vec![vec!["#".to_string(); n]; n]; + let mut cols = vec![false; n]; // 列にクイーンがあるか記録 + let mut diags1 = vec![false; 2 * n - 1]; // 主対角線にクイーンがあるかを記録 + let mut diags2 = vec![false; 2 * n - 1]; // 副対角線にクイーンがあるかを記録 + let mut res: Vec>> = Vec::new(); + + backtrack( + 0, + n, + &mut state, + &mut res, + &mut cols, + &mut diags1, + &mut diags2, + ); + + res +} + +/* Driver Code */ +pub fn main() { + let n: usize = 4; + let res = n_queens(n); + + println!("盤面の縦横は {n}"); + println!("クイーンの配置方法は全部で {} 通り", res.len()); + for state in res.iter() { + println!("--------------------"); + for row in state.iter() { + println!("{:?}", row); + } + } +} diff --git a/ja/codes/rust/chapter_backtracking/permutations_i.rs b/ja/codes/rust/chapter_backtracking/permutations_i.rs new file mode 100644 index 000000000..01a193f12 --- /dev/null +++ b/ja/codes/rust/chapter_backtracking/permutations_i.rs @@ -0,0 +1,46 @@ +/* + * File: permutations_i.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +/* バックトラッキング:順列 I */ +fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { + // 状態の長さが要素数に等しければ、解を記録 + if state.len() == choices.len() { + res.push(state); + return; + } + // すべての選択肢を走査 + for i in 0..choices.len() { + let choice = choices[i]; + // 枝刈り:要素の重複選択を許可しない + if !selected[i] { + // 試行: 選択を行い、状態を更新 + selected[i] = true; + state.push(choice); + // 次の選択へ進む + backtrack(state.clone(), choices, selected, res); + // バックトラック:選択を取り消し、前の状態に戻す + selected[i] = false; + state.pop(); + } + } +} + +/* 全順列 I */ +fn permutations_i(nums: &mut [i32]) -> Vec> { + let mut res = Vec::new(); // 状態(部分集合) + backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let mut nums = [1, 2, 3]; + + let res = permutations_i(&mut nums); + + println!("入力配列 nums = {:?}", &nums); + println!("すべての順列 res = {:?}", &res); +} diff --git a/ja/codes/rust/chapter_backtracking/permutations_ii.rs b/ja/codes/rust/chapter_backtracking/permutations_ii.rs new file mode 100644 index 000000000..4493ff60e --- /dev/null +++ b/ja/codes/rust/chapter_backtracking/permutations_ii.rs @@ -0,0 +1,50 @@ +/* + * File: permutations_ii.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use std::collections::HashSet; + +/* バックトラッキング:順列 II */ +fn backtrack(mut state: Vec, choices: &[i32], selected: &mut [bool], res: &mut Vec>) { + // 状態の長さが要素数に等しければ、解を記録 + if state.len() == choices.len() { + res.push(state); + return; + } + // すべての選択肢を走査 + let mut duplicated = HashSet::::new(); + for i in 0..choices.len() { + let choice = choices[i]; + // 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない + if !selected[i] && !duplicated.contains(&choice) { + // 試行: 選択を行い、状態を更新 + duplicated.insert(choice); // 選択済みの要素値を記録 + selected[i] = true; + state.push(choice); + // 次の選択へ進む + backtrack(state.clone(), choices, selected, res); + // バックトラック:選択を取り消し、前の状態に戻す + selected[i] = false; + state.pop(); + } + } +} + +/* 全順列 II */ +fn permutations_ii(nums: &mut [i32]) -> Vec> { + let mut res = Vec::new(); + backtrack(Vec::new(), nums, &mut vec![false; nums.len()], &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let mut nums = [1, 2, 2]; + + let res = permutations_ii(&mut nums); + + println!("入力配列 nums = {:?}", &nums); + println!("すべての順列 res = {:?}", &res); +} diff --git a/ja/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs b/ja/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs new file mode 100644 index 000000000..8fe560fe5 --- /dev/null +++ b/ja/codes/rust/chapter_backtracking/preorder_traversal_i_compact.rs @@ -0,0 +1,41 @@ +/* + * File: preorder_traversal_i_compact.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use std::{cell::RefCell, rc::Rc}; + +/* 前順走査:例題 1 */ +fn pre_order(res: &mut Vec>>, root: Option<&Rc>>) { + if root.is_none() { + return; + } + if let Some(node) = root { + if node.borrow().val == 7 { + // 解を記録 + res.push(node.clone()); + } + pre_order(res, node.borrow().left.as_ref()); + pre_order(res, node.borrow().right.as_ref()); + } +} + +/* Driver Code */ +pub fn main() { + let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); + println!("二分木を初期化"); + print_util::print_tree(root.as_ref().unwrap()); + + // 先行順走査 + let mut res = Vec::new(); + pre_order(&mut res, root.as_ref()); + + println!("\n値が 7 のノードをすべて出力"); + let mut vals = Vec::new(); + for node in res { + vals.push(node.borrow().val) + } + println!("{:?}", vals); +} diff --git a/ja/codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs b/ja/codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs new file mode 100644 index 000000000..17e84524e --- /dev/null +++ b/ja/codes/rust/chapter_backtracking/preorder_traversal_ii_compact.rs @@ -0,0 +1,52 @@ +/* + * File: preorder_traversal_ii_compact.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use std::{cell::RefCell, rc::Rc}; + +/* 前順走査:例題 2 */ +fn pre_order( + res: &mut Vec>>>, + path: &mut Vec>>, + root: Option<&Rc>>, +) { + if root.is_none() { + return; + } + if let Some(node) = root { + // 試す + path.push(node.clone()); + if node.borrow().val == 7 { + // 解を記録 + res.push(path.clone()); + } + pre_order(res, path, node.borrow().left.as_ref()); + pre_order(res, path, node.borrow().right.as_ref()); + // バックトラック + path.pop(); + } +} + +/* Driver Code */ +pub fn main() { + let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); + println!("二分木を初期化"); + print_util::print_tree(root.as_ref().unwrap()); + + // 先行順走査 + let mut path = Vec::new(); + let mut res = Vec::new(); + pre_order(&mut res, &mut path, root.as_ref()); + + println!("\n根ノードからノード 7 までのすべての経路を出力"); + for path in res { + let mut vals = Vec::new(); + for node in path { + vals.push(node.borrow().val) + } + println!("{:?}", vals); + } +} diff --git a/ja/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs b/ja/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs new file mode 100644 index 000000000..1c4500759 --- /dev/null +++ b/ja/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs @@ -0,0 +1,53 @@ +/* + * File: preorder_traversal_iii_compact.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use std::{cell::RefCell, rc::Rc}; + +/* 前順走査:例題 3 */ +fn pre_order( + res: &mut Vec>>>, + path: &mut Vec>>, + root: Option<&Rc>>, +) { + // 枝刈り + if root.is_none() || root.as_ref().unwrap().borrow().val == 3 { + return; + } + if let Some(node) = root { + // 試す + path.push(node.clone()); + if node.borrow().val == 7 { + // 解を記録 + res.push(path.clone()); + } + pre_order(res, path, node.borrow().left.as_ref()); + pre_order(res, path, node.borrow().right.as_ref()); + // バックトラック + path.pop(); + } +} + +/* Driver Code */ +pub fn main() { + let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); + println!("二分木を初期化"); + print_util::print_tree(root.as_ref().unwrap()); + + // 先行順走査 + let mut path = Vec::new(); + let mut res = Vec::new(); + pre_order(&mut res, &mut path, root.as_ref()); + + println!("\n根ノードからノード 7 までのすべての経路を出力し、経路には値が 3 のノードを含めない"); + for path in res { + let mut vals = Vec::new(); + for node in path { + vals.push(node.borrow().val) + } + println!("{:?}", vals); + } +} diff --git a/ja/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs b/ja/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs new file mode 100644 index 000000000..973ffc208 --- /dev/null +++ b/ja/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs @@ -0,0 +1,88 @@ +/* + * File: preorder_traversal_iii_template.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use std::{cell::RefCell, rc::Rc}; + +/* 現在の状態が解かどうかを判定 */ +fn is_solution(state: &mut Vec>>) -> bool { + return !state.is_empty() && state.last().unwrap().borrow().val == 7; +} + +/* 解を記録 */ +fn record_solution( + state: &mut Vec>>, + res: &mut Vec>>>, +) { + res.push(state.clone()); +} + +/* 現在の状態で、この選択が有効かどうかを判定 */ +fn is_valid(_: &mut Vec>>, choice: Option<&Rc>>) -> bool { + return choice.is_some() && choice.unwrap().borrow().val != 3; +} + +/* 状態を更新 */ +fn make_choice(state: &mut Vec>>, choice: Rc>) { + state.push(choice); +} + +/* 状態を元に戻す */ +fn undo_choice(state: &mut Vec>>, _: Rc>) { + state.pop(); +} + +/* バックトラッキング:例題 3 */ +fn backtrack( + state: &mut Vec>>, + choices: &Vec>>>, + res: &mut Vec>>>, +) { + // 解かどうかを確認 + if is_solution(state) { + // 解を記録 + record_solution(state, res); + } + // すべての選択肢を走査 + for &choice in choices.iter() { + // 枝刈り:選択が妥当かを確認する + if is_valid(state, choice) { + // 試行: 選択を行い、状態を更新 + make_choice(state, choice.unwrap().clone()); + // 次の選択へ進む + backtrack( + state, + &vec![ + choice.unwrap().borrow().left.as_ref(), + choice.unwrap().borrow().right.as_ref(), + ], + res, + ); + // バックトラック:選択を取り消し、前の状態に戻す + undo_choice(state, choice.unwrap().clone()); + } + } +} + +/* Driver Code */ +pub fn main() { + let root = vec_to_tree([1, 7, 3, 4, 5, 6, 7].map(|x| Some(x)).to_vec()); + println!("二分木を初期化"); + print_util::print_tree(root.as_ref().unwrap()); + + // バックトラッキング法 + let mut res = Vec::new(); + backtrack(&mut Vec::new(), &mut vec![root.as_ref()], &mut res); + + println!("\n根ノードからノード 7 までのすべての経路を出力し、経路に値が 3 のノードを含まないことを条件とする"); + for path in res { + let mut vals = Vec::new(); + for node in path { + vals.push(node.borrow().val) + } + println!("{:?}", vals); + } +} diff --git a/ja/codes/rust/chapter_backtracking/subset_sum_i.rs b/ja/codes/rust/chapter_backtracking/subset_sum_i.rs new file mode 100644 index 000000000..a6ebdcb42 --- /dev/null +++ b/ja/codes/rust/chapter_backtracking/subset_sum_i.rs @@ -0,0 +1,56 @@ +/* + * File: subset_sum_i.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* バックトラッキング:部分和 I */ +fn backtrack( + state: &mut Vec, + target: i32, + choices: &[i32], + start: usize, + res: &mut Vec>, +) { + // 部分集合の和が target に等しければ、解を記録 + if target == 0 { + res.push(state.clone()); + return; + } + // すべての選択肢を走査 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + for i in start..choices.len() { + // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため + if target - choices[i] < 0 { + break; + } + // 試す:選択を行い、target と start を更新 + state.push(choices[i]); + // 次の選択へ進む + backtrack(state, target - choices[i], choices, i, res); + // バックトラック:選択を取り消し、前の状態に戻す + state.pop(); + } +} + +/* 部分和 I を解く */ +fn subset_sum_i(nums: &mut [i32], target: i32) -> Vec> { + let mut state = Vec::new(); // 状態(部分集合) + nums.sort(); // nums をソート + let start = 0; // 開始点を走査 + let mut res = Vec::new(); // 結果リスト(部分集合のリスト) + backtrack(&mut state, target, nums, start, &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let mut nums = [3, 4, 5]; + let target = 9; + + let res = subset_sum_i(&mut nums, target); + + println!("入力配列 nums = {:?}, target = {}", &nums, target); + println!("和が {} に等しいすべての部分集合 res = {:?}", target, &res); +} diff --git a/ja/codes/rust/chapter_backtracking/subset_sum_i_naive.rs b/ja/codes/rust/chapter_backtracking/subset_sum_i_naive.rs new file mode 100644 index 000000000..c3111b17f --- /dev/null +++ b/ja/codes/rust/chapter_backtracking/subset_sum_i_naive.rs @@ -0,0 +1,54 @@ +/* + * File: subset_sum_i_naive.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* バックトラッキング:部分和 I */ +fn backtrack( + state: &mut Vec, + target: i32, + total: i32, + choices: &[i32], + res: &mut Vec>, +) { + // 部分集合の和が target に等しければ、解を記録 + if total == target { + res.push(state.clone()); + return; + } + // すべての選択肢を走査 + for i in 0..choices.len() { + // 枝刈り:部分和が target を超える場合はその選択をスキップする + if total + choices[i] > target { + continue; + } + // 試行:選択を行い、要素と total を更新する + state.push(choices[i]); + // 次の選択へ進む + backtrack(state, target, total + choices[i], choices, res); + // バックトラック:選択を取り消し、前の状態に戻す + state.pop(); + } +} + +/* 部分和 I を解く(重複部分集合を含む) */ +fn subset_sum_i_naive(nums: &[i32], target: i32) -> Vec> { + let mut state = Vec::new(); // 状態(部分集合) + let total = 0; // 部分和 + let mut res = Vec::new(); // 結果リスト(部分集合のリスト) + backtrack(&mut state, target, total, nums, &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let nums = [3, 4, 5]; + let target = 9; + + let res = subset_sum_i_naive(&nums, target); + + println!("入力配列 nums = {:?}, target = {}", &nums, target); + println!("和が {} に等しいすべての部分集合 res = {:?}", target, &res); + println!("注意: この方法の出力結果には重複した集合が含まれます"); +} diff --git a/ja/codes/rust/chapter_backtracking/subset_sum_ii.rs b/ja/codes/rust/chapter_backtracking/subset_sum_ii.rs new file mode 100644 index 000000000..8a624aaac --- /dev/null +++ b/ja/codes/rust/chapter_backtracking/subset_sum_ii.rs @@ -0,0 +1,61 @@ +/* + * File: subset_sum_ii.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* バックトラッキング:部分和 II */ +fn backtrack( + state: &mut Vec, + target: i32, + choices: &[i32], + start: usize, + res: &mut Vec>, +) { + // 部分集合の和が target に等しければ、解を記録 + if target == 0 { + res.push(state.clone()); + return; + } + // すべての選択肢を走査 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける + for i in start..choices.len() { + // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため + if target - choices[i] < 0 { + break; + } + // 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする + if i > start && choices[i] == choices[i - 1] { + continue; + } + // 試す:選択を行い、target と start を更新 + state.push(choices[i]); + // 次の選択へ進む + backtrack(state, target - choices[i], choices, i + 1, res); + // バックトラック:選択を取り消し、前の状態に戻す + state.pop(); + } +} + +/* 部分和 II を解く */ +fn subset_sum_ii(nums: &mut [i32], target: i32) -> Vec> { + let mut state = Vec::new(); // 状態(部分集合) + nums.sort(); // nums をソート + let start = 0; // 開始点を走査 + let mut res = Vec::new(); // 結果リスト(部分集合のリスト) + backtrack(&mut state, target, nums, start, &mut res); + res +} + +/* Driver Code */ +pub fn main() { + let mut nums = [4, 4, 5]; + let target = 9; + + let res = subset_sum_ii(&mut nums, target); + + println!("入力配列 nums = {:?}, target = {}", &nums, target); + println!("和が {} に等しいすべての部分集合 res = {:?}", target, &res); +} diff --git a/ja/codes/rust/chapter_computational_complexity/iteration.rs b/ja/codes/rust/chapter_computational_complexity/iteration.rs new file mode 100644 index 000000000..6a31ea75e --- /dev/null +++ b/ja/codes/rust/chapter_computational_complexity/iteration.rs @@ -0,0 +1,74 @@ +/* + * File: iteration.rs + * Created Time: 2023-09-02 + * Author: night-cruise (2586447362@qq.com) + */ + +/* for ループ */ +fn for_loop(n: i32) -> i32 { + let mut res = 0; + // 1, 2, ..., n-1, n を順に加算する + for i in 1..=n { + res += i; + } + res +} + +/* while ループ */ +fn while_loop(n: i32) -> i32 { + let mut res = 0; + let mut i = 1; // 条件変数を初期化する + + // 1, 2, ..., n-1, n を順に加算する + while i <= n { + res += i; + i += 1; // 条件変数を更新する + } + res +} + +/* while ループ(2回更新) */ +fn while_loop_ii(n: i32) -> i32 { + let mut res = 0; + let mut i = 1; // 条件変数を初期化する + + // 1, 4, 10, ... を順に加算する + while i <= n { + res += i; + // 条件変数を更新する + i += 1; + i *= 2; + } + res +} + +/* 二重 for ループ */ +fn nested_for_loop(n: i32) -> String { + let mut res = vec![]; + // i = 1, 2, ..., n-1, n とループする + for i in 1..=n { + // j = 1, 2, ..., n-1, n とループする + for j in 1..=n { + res.push(format!("({}, {}), ", i, j)); + } + } + res.join("") +} + +/* Driver Code */ +fn main() { + let n = 5; + let mut res; + + res = for_loop(n); + println!("\nfor ループの合計結果 res = {res}"); + + res = while_loop(n); + println!("\nwhile ループの合計結果 res = {res}"); + + res = while_loop_ii(n); + println!("\nwhile ループ(2 回更新)の合計結果 res = {}", res); + + let res = nested_for_loop(n); + println!("\n二重 for ループの走査結果 {res}"); +} diff --git a/ja/codes/rust/chapter_computational_complexity/recursion.rs b/ja/codes/rust/chapter_computational_complexity/recursion.rs new file mode 100644 index 000000000..bbb554b42 --- /dev/null +++ b/ja/codes/rust/chapter_computational_complexity/recursion.rs @@ -0,0 +1,76 @@ +/* + * File: recursion.rs + * Created Time: 2023-09-02 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 再帰 */ +fn recur(n: i32) -> i32 { + // 終了条件 + if n == 1 { + return 1; + } + // 再帰:再帰呼び出し + let res = recur(n - 1); + // 帰りがけ:結果を返す + n + res +} + +/* 反復で再帰を模擬する */ +fn for_loop_recur(n: i32) -> i32 { + // 明示的なスタックを使ってシステムコールスタックを模擬する + let mut stack = Vec::new(); + let mut res = 0; + // 再帰:再帰呼び出し + for i in (1..=n).rev() { + // 「スタックへのプッシュ」で「再帰」を模擬する + stack.push(i); + } + // 帰りがけ:結果を返す + while !stack.is_empty() { + // 「スタックから取り出す操作」で「帰り」をシミュレート + res += stack.pop().unwrap(); + } + // res = 1+2+3+...+n + res +} + +/* 末尾再帰 */ +fn tail_recur(n: i32, res: i32) -> i32 { + // 終了条件 + if n == 0 { + return res; + } + // 末尾再帰呼び出し + tail_recur(n - 1, res + n) +} + +/* フィボナッチ数列:再帰 */ +fn fib(n: i32) -> i32 { + // 終了条件 f(1) = 0, f(2) = 1 + if n == 1 || n == 2 { + return n - 1; + } + // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す + let res = fib(n - 1) + fib(n - 2); + // 結果を返す + res +} + +/* Driver Code */ +fn main() { + let n = 5; + let mut res; + + res = recur(n); + println!("\n再帰関数による合計結果 res = {res}"); + + res = for_loop_recur(n); + println!("\n反復で再帰を模擬した合計結果 res = {res}"); + + res = tail_recur(n, 0); + println!("\n末尾再帰関数による合計結果 res = {res}"); + + res = fib(n); + println!("\nフィボナッチ数列の第 {n} 項は {res}"); +} diff --git a/ja/codes/rust/chapter_computational_complexity/space_complexity.rs b/ja/codes/rust/chapter_computational_complexity/space_complexity.rs new file mode 100644 index 000000000..b4fb34909 --- /dev/null +++ b/ja/codes/rust/chapter_computational_complexity/space_complexity.rs @@ -0,0 +1,114 @@ +/* + * File: space_complexity.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, ListNode, TreeNode}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +/* 関数 */ +fn function() -> i32 { + // 何らかの処理を行う + return 0; +} + +/* 定数階 */ +#[allow(unused)] +fn constant(n: i32) { + // 定数、変数、オブジェクトは O(1) の空間を占める + const A: i32 = 0; + let b = 0; + let nums = vec![0; 10000]; + let node = ListNode::new(0); + // ループ内の変数は O(1) の空間を占める + for i in 0..n { + let c = 0; + } + // ループ内の関数は O(1) の空間を占める + for i in 0..n { + function(); + } +} + +/* 線形階 */ +#[allow(unused)] +fn linear(n: i32) { + // 長さ n の配列は O(n) の空間を使用 + let mut nums = vec![0; n as usize]; + // 長さ n のリストは O(n) の空間を使用 + let mut nodes = Vec::new(); + for i in 0..n { + nodes.push(ListNode::new(i)) + } + // 長さ n のハッシュテーブルは O(n) の空間を使用 + let mut map = HashMap::new(); + for i in 0..n { + map.insert(i, i.to_string()); + } +} + +/* 線形時間(再帰実装) */ +fn linear_recur(n: i32) { + println!("再帰 n = {}", n); + if n == 1 { + return; + }; + linear_recur(n - 1); +} + +/* 二乗階 */ +#[allow(unused)] +fn quadratic(n: i32) { + // 行列は O(n^2) の空間を使用する + let num_matrix = vec![vec![0; n as usize]; n as usize]; + // 二次元リストは O(n^2) の空間を使用 + let mut num_list = Vec::new(); + for i in 0..n { + let mut tmp = Vec::new(); + for j in 0..n { + tmp.push(0); + } + num_list.push(tmp); + } +} + +/* 二次時間(再帰実装) */ +fn quadratic_recur(n: i32) -> i32 { + if n <= 0 { + return 0; + }; + // 配列 nums の長さは n, n-1, ..., 2, 1 + let nums = vec![0; n as usize]; + println!("再帰 n = {} における nums の長さ = {}", n, nums.len()); + return quadratic_recur(n - 1); +} + +/* 指数時間(完全二分木の構築) */ +fn build_tree(n: i32) -> Option>> { + if n == 0 { + return None; + }; + let root = TreeNode::new(0); + root.borrow_mut().left = build_tree(n - 1); + root.borrow_mut().right = build_tree(n - 1); + return Some(root); +} + +/* Driver Code */ +fn main() { + let n = 5; + // 定数階 + constant(n); + // 線形階 + linear(n); + linear_recur(n); + // 二乗階 + quadratic(n); + quadratic_recur(n); + // 指数オーダー + let root = build_tree(n); + print_util::print_tree(&root.unwrap()); +} diff --git a/ja/codes/rust/chapter_computational_complexity/time_complexity.rs b/ja/codes/rust/chapter_computational_complexity/time_complexity.rs new file mode 100644 index 000000000..a3724fdd1 --- /dev/null +++ b/ja/codes/rust/chapter_computational_complexity/time_complexity.rs @@ -0,0 +1,170 @@ +/* + * File: time_complexity.rs + * Created Time: 2023-01-10 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ + +/* 定数階 */ +fn constant(n: i32) -> i32 { + _ = n; + let mut count = 0; + let size = 100_000; + for _ in 0..size { + count += 1; + } + count +} + +/* 線形階 */ +fn linear(n: i32) -> i32 { + let mut count = 0; + for _ in 0..n { + count += 1; + } + count +} + +/* 線形時間(配列を走査) */ +fn array_traversal(nums: &[i32]) -> i32 { + let mut count = 0; + // ループ回数は配列長に比例する + for _ in nums { + count += 1; + } + count +} + +/* 二乗階 */ +fn quadratic(n: i32) -> i32 { + let mut count = 0; + // ループ回数はデータサイズ n の二乗に比例する + for _ in 0..n { + for _ in 0..n { + count += 1; + } + } + count +} + +/* 二次時間(バブルソート) */ +fn bubble_sort(nums: &mut [i32]) -> i32 { + let mut count = 0; // カウンタ + + // 外側のループ:未ソート区間は [0, i] + for i in (1..nums.len()).rev() { + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for j in 0..i { + if nums[j] > nums[j + 1] { + // nums[j] と nums[j + 1] を交換 + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 要素交換には 3 回の単位操作が含まれる + } + } + } + count +} + +/* 指数時間(ループ実装) */ +fn exponential(n: i32) -> i32 { + let mut count = 0; + let mut base = 1; + // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する + for _ in 0..n { + for _ in 0..base { + count += 1 + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + count +} + +/* 指数時間(再帰実装) */ +fn exp_recur(n: i32) -> i32 { + if n == 1 { + return 1; + } + exp_recur(n - 1) + exp_recur(n - 1) + 1 +} + +/* 対数時間(ループ実装) */ +fn logarithmic(mut n: i32) -> i32 { + let mut count = 0; + while n > 1 { + n = n / 2; + count += 1; + } + count +} + +/* 対数時間(再帰実装) */ +fn log_recur(n: i32) -> i32 { + if n <= 1 { + return 0; + } + log_recur(n / 2) + 1 +} + +/* 線形対数時間 */ +fn linear_log_recur(n: i32) -> i32 { + if n <= 1 { + return 1; + } + let mut count = linear_log_recur(n / 2) + linear_log_recur(n / 2); + for _ in 0..n { + count += 1; + } + return count; +} + +/* 階乗時間(再帰実装) */ +fn factorial_recur(n: i32) -> i32 { + if n == 0 { + return 1; + } + let mut count = 0; + // 1個から n 個に分裂 + for _ in 0..n { + count += factorial_recur(n - 1); + } + count +} + +/* Driver Code */ +fn main() { + // n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる + let n: i32 = 8; + println!("入力データサイズ n = {}", n); + + let mut count = constant(n); + println!("定数時間の操作回数 = {}", count); + + count = linear(n); + println!("線形時間の操作回数 = {}", count); + count = array_traversal(&vec![0; n as usize]); + println!("線形時間(配列の走査)の操作回数 = {}", count); + + count = quadratic(n); + println!("二乗時間の操作回数 = {}", count); + let mut nums = (1..=n).rev().collect::>(); // [n,n-1,...,2,1] + count = bubble_sort(&mut nums); + println!("二乗時間(バブルソート)の操作回数 = {}", count); + + count = exponential(n); + println!("指数時間(ループ実装)の操作回数 = {}", count); + count = exp_recur(n); + println!("指数時間(再帰実装)の操作回数 = {}", count); + + count = logarithmic(n); + println!("対数時間(ループ実装)の操作回数 = {}", count); + count = log_recur(n); + println!("対数時間(再帰実装)の操作回数 = {}", count); + + count = linear_log_recur(n); + println!("線形対数時間(再帰実装)の操作回数 = {}", count); + + count = factorial_recur(n); + println!("階乗時間(再帰実装)の操作回数 = {}", count); +} diff --git a/ja/codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs b/ja/codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs new file mode 100644 index 000000000..8bf6b5150 --- /dev/null +++ b/ja/codes/rust/chapter_computational_complexity/worst_best_time_complexity.rs @@ -0,0 +1,42 @@ +/* + * File: worst_best_time_complexity.rs + * Created Time: 2023-01-13 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; +use rand::seq::SliceRandom; +use rand::thread_rng; + +/* 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 */ +fn random_numbers(n: i32) -> Vec { + // 配列 nums = { 1, 2, 3, ..., n } を生成 + let mut nums = (1..=n).collect::>(); + // 配列要素をランダムにシャッフル + nums.shuffle(&mut thread_rng()); + nums +} + +/* 配列 nums 内で数値 1 のインデックスを探す */ +fn find_one(nums: &[i32]) -> Option { + for i in 0..nums.len() { + // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる + // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる + if nums[i] == 1 { + return Some(i); + } + } + None +} + +/* Driver Code */ +fn main() { + for _ in 0..10 { + let n = 100; + let nums = random_numbers(n); + let index = find_one(&nums).unwrap(); + print!("\n配列 [ 1, 2, ..., n ] をシャッフルした後 = "); + print_util::print_array(&nums); + println!("\n数字 1 のインデックスは {}", index); + } +} diff --git a/ja/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs b/ja/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs new file mode 100644 index 000000000..0809e1b9d --- /dev/null +++ b/ja/codes/rust/chapter_divide_and_conquer/binary_search_recur.rs @@ -0,0 +1,41 @@ +/* + * File: binary_search_recur.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 二分探索:問題 f(i, j) */ +fn dfs(nums: &[i32], target: i32, i: i32, j: i32) -> i32 { + // 区間が空なら対象要素は存在しないので -1 を返す + if i > j { + return -1; + } + let m: i32 = i + (j - i) / 2; + if nums[m as usize] < target { + // 部分問題 f(m+1, j) を再帰的に解く + return dfs(nums, target, m + 1, j); + } else if nums[m as usize] > target { + // 部分問題 f(i, m-1) を再帰的に解く + return dfs(nums, target, i, m - 1); + } else { + // 目標要素が見つかったらそのインデックスを返す + return m; + } +} + +/* 二分探索 */ +fn binary_search(nums: &[i32], target: i32) -> i32 { + let n = nums.len() as i32; + // 問題 f(0, n-1) を解く + dfs(nums, target, 0, n - 1) +} + +/* Driver Code */ +pub fn main() { + let target = 6; + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + // 二分探索(両閉区間) + let index = binary_search(&nums, target); + println!("目的の要素 6 のインデックス = {index}"); +} diff --git a/ja/codes/rust/chapter_divide_and_conquer/build_tree.rs b/ja/codes/rust/chapter_divide_and_conquer/build_tree.rs new file mode 100644 index 000000000..d63680894 --- /dev/null +++ b/ja/codes/rust/chapter_divide_and_conquer/build_tree.rs @@ -0,0 +1,56 @@ +/* + * File: build_tree.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, TreeNode}; +use std::collections::HashMap; +use std::{cell::RefCell, rc::Rc}; + +/* 二分木を構築:分割統治 */ +fn dfs( + preorder: &[i32], + inorder_map: &HashMap, + i: i32, + l: i32, + r: i32, +) -> Option>> { + // 部分木区間が空なら終了する + if r - l < 0 { + return None; + } + // ルートノードを初期化する + let root = TreeNode::new(preorder[i as usize]); + // m を求めて左右部分木を分割する + let m = inorder_map.get(&preorder[i as usize]).unwrap(); + // 部分問題:左部分木を構築する + root.borrow_mut().left = dfs(preorder, inorder_map, i + 1, l, m - 1); + // 部分問題:右部分木を構築する + root.borrow_mut().right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r); + // 根ノードを返す + Some(root) +} + +/* 二分木を構築 */ +fn build_tree(preorder: &[i32], inorder: &[i32]) -> Option>> { + // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する + let mut inorder_map: HashMap = HashMap::new(); + for i in 0..inorder.len() { + inorder_map.insert(inorder[i], i as i32); + } + let root = dfs(preorder, &inorder_map, 0, 0, inorder.len() as i32 - 1); + root +} + +/* Driver Code */ +fn main() { + let preorder = [3, 9, 2, 1, 7]; + let inorder = [9, 3, 1, 2, 7]; + println!("中順走査 = {:?}", preorder); + println!("前順走査 = {:?}", inorder); + + let root = build_tree(&preorder, &inorder); + println!("構築した二分木は:"); + print_util::print_tree(root.as_ref().unwrap()); +} diff --git a/ja/codes/rust/chapter_divide_and_conquer/hanota.rs b/ja/codes/rust/chapter_divide_and_conquer/hanota.rs new file mode 100644 index 000000000..ff021703b --- /dev/null +++ b/ja/codes/rust/chapter_divide_and_conquer/hanota.rs @@ -0,0 +1,55 @@ +/* + * File: hanota.rs + * Created Time: 2023-07-15 + * Author: codingonion (coderonion@gmail.com) + */ + +#![allow(non_snake_case)] + +/* 円盤を 1 枚移動 */ +fn move_pan(src: &mut Vec, tar: &mut Vec) { + // src の上から円盤を1枚取り出す + let pan = src.pop().unwrap(); + // 円盤を tar の上に置く + tar.push(pan); +} + +/* ハノイの塔の問題 f(i) を解く */ +fn dfs(i: i32, src: &mut Vec, buf: &mut Vec, tar: &mut Vec) { + // src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す + if i == 1 { + move_pan(src, tar); + return; + } + // 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す + dfs(i - 1, src, tar, buf); + // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す + move_pan(src, tar); + // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す + dfs(i - 1, buf, src, tar); +} + +/* ハノイの塔を解く */ +fn solve_hanota(A: &mut Vec, B: &mut Vec, C: &mut Vec) { + let n = A.len() as i32; + // A の上から n 枚の円盤を B を介して C へ移す + dfs(n, A, B, C); +} + +/* Driver Code */ +pub fn main() { + let mut A = vec![5, 4, 3, 2, 1]; + let mut B = Vec::new(); + let mut C = Vec::new(); + println!("初期状態:"); + println!("A = {:?}", A); + println!("B = {:?}", B); + println!("C = {:?}", C); + + solve_hanota(&mut A, &mut B, &mut C); + + println!("円盤の移動完了後:"); + println!("A = {:?}", A); + println!("B = {:?}", B); + println!("C = {:?}", C); +} diff --git a/ja/codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs b/ja/codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs new file mode 100644 index 000000000..0f955d85b --- /dev/null +++ b/ja/codes/rust/chapter_dynamic_programming/climbing_stairs_backtrack.rs @@ -0,0 +1,41 @@ +/* + * File: climbing_stairs_backtrack.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* バックトラッキング */ +fn backtrack(choices: &[i32], state: i32, n: i32, res: &mut [i32]) { + // 第 n 段に到達したら、方法数を 1 増やす + if state == n { + res[0] = res[0] + 1; + } + // すべての選択肢を走査 + for &choice in choices { + // 枝刈り: 第 n 段を超えないようにする + if state + choice > n { + continue; + } + // 試行: 選択を行い、状態を更新 + backtrack(choices, state + choice, n, res); + // バックトラック + } +} + +/* 階段登り:バックトラッキング */ +fn climbing_stairs_backtrack(n: usize) -> i32 { + let choices = vec![1, 2]; // 1 段または 2 段上ることを選べる + let state = 0; // 第 0 段から上り始める + let mut res = Vec::new(); + res.push(0); // res[0] を使って方法数を記録する + backtrack(&choices, state, n as i32, &mut res); + res[0] +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_backtrack(n); + println!("{n} 段の階段を上る方法は全部で {res} 通り"); +} diff --git a/ja/codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs b/ja/codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs new file mode 100644 index 000000000..ebe5173e6 --- /dev/null +++ b/ja/codes/rust/chapter_dynamic_programming/climbing_stairs_constraint_dp.rs @@ -0,0 +1,33 @@ +/* + * File: climbing_stairs_constraint_dp.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 制約付き階段登り:動的計画法 */ +fn climbing_stairs_constraint_dp(n: usize) -> i32 { + if n == 1 || n == 2 { + return 1; + }; + // 部分問題の解を保存するために dp テーブルを初期化 + let mut dp = vec![vec![-1; 3]; n + 1]; + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for i in 3..=n { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + dp[n][1] + dp[n][2] +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_constraint_dp(n); + println!("{n} 段の階段を上る方法は全部で {res} 通り"); +} diff --git a/ja/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs b/ja/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs new file mode 100644 index 000000000..e1d4f4b68 --- /dev/null +++ b/ja/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs.rs @@ -0,0 +1,29 @@ +/* + * File: climbing_stairs_dfs.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 検索 */ +fn dfs(i: usize) -> i32 { + // dp[1] と dp[2] は既知なので返す + if i == 1 || i == 2 { + return i as i32; + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i - 1) + dfs(i - 2); + count +} + +/* 階段登り:探索 */ +fn climbing_stairs_dfs(n: usize) -> i32 { + dfs(n) +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_dfs(n); + println!("{n} 段の階段を上る方法は全部で {res} 通り"); +} diff --git a/ja/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs b/ja/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs new file mode 100644 index 000000000..7c2cbc7f7 --- /dev/null +++ b/ja/codes/rust/chapter_dynamic_programming/climbing_stairs_dfs_mem.rs @@ -0,0 +1,37 @@ +/* + * File: climbing_stairs_dfs_mem.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* メモ化探索 */ +fn dfs(i: usize, mem: &mut [i32]) -> i32 { + // dp[1] と dp[2] は既知なので返す + if i == 1 || i == 2 { + return i as i32; + } + // dp[i] の記録があれば、それをそのまま返す + if mem[i] != -1 { + return mem[i]; + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i - 1, mem) + dfs(i - 2, mem); + // dp[i] を記録する + mem[i] = count; + count +} + +/* 階段登り:メモ化探索 */ +fn climbing_stairs_dfs_mem(n: usize) -> i32 { + // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す + let mut mem = vec![-1; n + 1]; + dfs(n, &mut mem) +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_dfs_mem(n); + println!("{n} 段の階段を上る方法は全部で {res} 通り"); +} diff --git a/ja/codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs b/ja/codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs new file mode 100644 index 000000000..9bd7a0c9b --- /dev/null +++ b/ja/codes/rust/chapter_dynamic_programming/climbing_stairs_dp.rs @@ -0,0 +1,48 @@ +/* + * File: climbing_stairs_dp.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 階段登り:動的計画法 */ +fn climbing_stairs_dp(n: usize) -> i32 { + // dp[1] と dp[2] は既知なので返す + if n == 1 || n == 2 { + return n as i32; + } + // 部分問題の解を保存するために dp テーブルを初期化 + let mut dp = vec![-1; n + 1]; + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1] = 1; + dp[2] = 2; + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for i in 3..=n { + dp[i] = dp[i - 1] + dp[i - 2]; + } + dp[n] +} + +/* 階段登り:空間最適化した動的計画法 */ +fn climbing_stairs_dp_comp(n: usize) -> i32 { + if n == 1 || n == 2 { + return n as i32; + } + let (mut a, mut b) = (1, 2); + for _ in 3..=n { + let tmp = b; + b = a + b; + a = tmp; + } + b +} + +/* Driver Code */ +pub fn main() { + let n: usize = 9; + + let res = climbing_stairs_dp(n); + println!("{n} 段の階段を上る方法は全部で {res} 通り"); + + let res = climbing_stairs_dp_comp(n); + println!("{n} 段の階段を上る方法は全部で {res} 通り"); +} diff --git a/ja/codes/rust/chapter_dynamic_programming/coin_change.rs b/ja/codes/rust/chapter_dynamic_programming/coin_change.rs new file mode 100644 index 000000000..1d7b5dac2 --- /dev/null +++ b/ja/codes/rust/chapter_dynamic_programming/coin_change.rs @@ -0,0 +1,75 @@ +/* + * File: coin_change.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* コイン両替:動的計画法 */ +fn coin_change_dp(coins: &[i32], amt: usize) -> i32 { + let n = coins.len(); + let max = amt + 1; + // dp テーブルを初期化 + let mut dp = vec![vec![0; amt + 1]; n + 1]; + // 状態遷移:先頭行と先頭列 + for a in 1..=amt { + dp[0][a] = max; + } + // 状態遷移: 残りの行と列 + for i in 1..=n { + for a in 1..=amt { + if coins[i - 1] > a as i32 { + // 目標金額を超えるなら硬貨 i は選ばない + dp[i][a] = dp[i - 1][a]; + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[i][a] = std::cmp::min(dp[i - 1][a], dp[i][a - coins[i - 1] as usize] + 1); + } + } + } + if dp[n][amt] != max { + return dp[n][amt] as i32; + } else { + -1 + } +} + +/* コイン交換:空間最適化後の動的計画法 */ +fn coin_change_dp_comp(coins: &[i32], amt: usize) -> i32 { + let n = coins.len(); + let max = amt + 1; + // dp テーブルを初期化 + let mut dp = vec![0; amt + 1]; + dp.fill(max); + dp[0] = 0; + // 状態遷移 + for i in 1..=n { + for a in 1..=amt { + if coins[i - 1] > a as i32 { + // 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a]; + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[a] = std::cmp::min(dp[a], dp[a - coins[i - 1] as usize] + 1); + } + } + } + if dp[amt] != max { + return dp[amt] as i32; + } else { + -1 + } +} + +/* Driver Code */ +pub fn main() { + let coins = [1, 2, 5]; + let amt: usize = 4; + + // 動的計画法 + let res = coin_change_dp(&coins, amt); + println!("目標金額にするために必要な最小硬貨枚数は {res}"); + + // 空間最適化後の動的計画法 + let res = coin_change_dp_comp(&coins, amt); + println!("目標金額にするために必要な最小硬貨枚数は {res}"); +} diff --git a/ja/codes/rust/chapter_dynamic_programming/coin_change_ii.rs b/ja/codes/rust/chapter_dynamic_programming/coin_change_ii.rs new file mode 100644 index 000000000..4f648dc6a --- /dev/null +++ b/ja/codes/rust/chapter_dynamic_programming/coin_change_ii.rs @@ -0,0 +1,64 @@ +/* + * File: coin_change_ii.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* コイン両替 II:動的計画法 */ +fn coin_change_ii_dp(coins: &[i32], amt: usize) -> i32 { + let n = coins.len(); + // dp テーブルを初期化 + let mut dp = vec![vec![0; amt + 1]; n + 1]; + // 先頭列を初期化する + for i in 0..=n { + dp[i][0] = 1; + } + // 状態遷移 + for i in 1..=n { + for a in 1..=amt { + if coins[i - 1] > a as i32 { + // 目標金額を超えるなら硬貨 i は選ばない + dp[i][a] = dp[i - 1][a]; + } else { + // コイン i を選ばない場合と選ぶ場合の和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1] as usize]; + } + } + } + dp[n][amt] +} + +/* コイン両替 II:空間最適化した動的計画法 */ +fn coin_change_ii_dp_comp(coins: &[i32], amt: usize) -> i32 { + let n = coins.len(); + // dp テーブルを初期化 + let mut dp = vec![0; amt + 1]; + dp[0] = 1; + // 状態遷移 + for i in 1..=n { + for a in 1..=amt { + if coins[i - 1] > a as i32 { + // 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a]; + } else { + // コイン i を選ばない場合と選ぶ場合の和 + dp[a] = dp[a] + dp[a - coins[i - 1] as usize]; + } + } + } + dp[amt] +} + +/* Driver Code */ +pub fn main() { + let coins = [1, 2, 5]; + let amt: usize = 5; + + // 動的計画法 + let res = coin_change_ii_dp(&coins, amt); + println!("目標金額を作る硬貨の組み合わせ数は {res}"); + + // 空間最適化後の動的計画法 + let res = coin_change_ii_dp_comp(&coins, amt); + println!("目標金額を作る硬貨の組み合わせ数は {res}"); +} diff --git a/ja/codes/rust/chapter_dynamic_programming/edit_distance.rs b/ja/codes/rust/chapter_dynamic_programming/edit_distance.rs new file mode 100644 index 000000000..5790ceadd --- /dev/null +++ b/ja/codes/rust/chapter_dynamic_programming/edit_distance.rs @@ -0,0 +1,145 @@ +/* + * File: edit_distance.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 編集距離:総当たり探索 */ +fn edit_distance_dfs(s: &str, t: &str, i: usize, j: usize) -> i32 { + // s と t がともに空なら 0 を返す + if i == 0 && j == 0 { + return 0; + } + // s が空なら t の長さを返す + if i == 0 { + return j as i32; + } + // t が空なら s の長さを返す + if j == 0 { + return i as i32; + } + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if s.chars().nth(i - 1) == t.chars().nth(j - 1) { + return edit_distance_dfs(s, t, i - 1, j - 1); + } + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + let insert = edit_distance_dfs(s, t, i, j - 1); + let delete = edit_distance_dfs(s, t, i - 1, j); + let replace = edit_distance_dfs(s, t, i - 1, j - 1); + // 最小編集回数を返す + std::cmp::min(std::cmp::min(insert, delete), replace) + 1 +} + +/* 編集距離:メモ化探索 */ +fn edit_distance_dfs_mem(s: &str, t: &str, mem: &mut Vec>, i: usize, j: usize) -> i32 { + // s と t がともに空なら 0 を返す + if i == 0 && j == 0 { + return 0; + } + // s が空なら t の長さを返す + if i == 0 { + return j as i32; + } + // t が空なら s の長さを返す + if j == 0 { + return i as i32; + } + // 記録済みなら、それをそのまま返す + if mem[i][j] != -1 { + return mem[i][j]; + } + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if s.chars().nth(i - 1) == t.chars().nth(j - 1) { + return edit_distance_dfs_mem(s, t, mem, i - 1, j - 1); + } + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + let insert = edit_distance_dfs_mem(s, t, mem, i, j - 1); + let delete = edit_distance_dfs_mem(s, t, mem, i - 1, j); + let replace = edit_distance_dfs_mem(s, t, mem, i - 1, j - 1); + // 最小編集回数を記録して返す + mem[i][j] = std::cmp::min(std::cmp::min(insert, delete), replace) + 1; + mem[i][j] +} + +/* 編集距離:動的計画法 */ +fn edit_distance_dp(s: &str, t: &str) -> i32 { + let (n, m) = (s.len(), t.len()); + let mut dp = vec![vec![0; m + 1]; n + 1]; + // 状態遷移:先頭行と先頭列 + for i in 1..=n { + dp[i][0] = i as i32; + } + for j in 1..m { + dp[0][j] = j as i32; + } + // 状態遷移: 残りの行と列 + for i in 1..=n { + for j in 1..=m { + if s.chars().nth(i - 1) == t.chars().nth(j - 1) { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[i][j] = + std::cmp::min(std::cmp::min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + dp[n][m] +} + +/* 編集距離:空間最適化した動的計画法 */ +fn edit_distance_dp_comp(s: &str, t: &str) -> i32 { + let (n, m) = (s.len(), t.len()); + let mut dp = vec![0; m + 1]; + // 状態遷移:先頭行 + for j in 1..m { + dp[j] = j as i32; + } + // 状態遷移:残りの行 + for i in 1..=n { + // 状態遷移:先頭列 + let mut leftup = dp[0]; // dp[i-1, j-1] を一時保存する + dp[0] = i as i32; + // 状態遷移:残りの列 + for j in 1..=m { + let temp = dp[j]; + if s.chars().nth(i - 1) == t.chars().nth(j - 1) { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[j] = leftup; + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[j] = std::cmp::min(std::cmp::min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 次の反復の dp[i-1, j-1] に更新する + } + } + dp[m] +} + +/* Driver Code */ +pub fn main() { + let s = "bag"; + let t = "pack"; + let (n, m) = (s.len(), t.len()); + + // 全探索 + let res = edit_distance_dfs(s, t, n, m); + println!("{s} を {t} に変更するには最小で {res} 回の編集が必要"); + + // メモ化探索 + let mut mem = vec![vec![0; m + 1]; n + 1]; + for row in mem.iter_mut() { + row.fill(-1); + } + let res = edit_distance_dfs_mem(s, t, &mut mem, n, m); + println!("{s} を {t} に変更するには最小で {res} 回の編集が必要"); + + // 動的計画法 + let res = edit_distance_dp(s, t); + println!("{s} を {t} に変更するには最小で {res} 回の編集が必要"); + + // 空間最適化後の動的計画法 + let res = edit_distance_dp_comp(s, t); + println!("{s} を {t} に変更するには最小で {res} 回の編集が必要"); +} diff --git a/ja/codes/rust/chapter_dynamic_programming/knapsack.rs b/ja/codes/rust/chapter_dynamic_programming/knapsack.rs new file mode 100644 index 000000000..fd2cc32d5 --- /dev/null +++ b/ja/codes/rust/chapter_dynamic_programming/knapsack.rs @@ -0,0 +1,113 @@ +/* + * File: knapsack.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 0-1 ナップサック:総当たり探索 */ +fn knapsack_dfs(wgt: &[i32], val: &[i32], i: usize, c: usize) -> i32 { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if i == 0 || c == 0 { + return 0; + } + // ナップサック容量を超える場合は、入れない選択しかできない + if wgt[i - 1] > c as i32 { + return knapsack_dfs(wgt, val, i - 1, c); + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + let no = knapsack_dfs(wgt, val, i - 1, c); + let yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1] as usize) + val[i - 1]; + // 2つの案のうち価値が大きいほうを返す + std::cmp::max(no, yes) +} + +/* 0-1 ナップサック:メモ化探索 */ +fn knapsack_dfs_mem(wgt: &[i32], val: &[i32], mem: &mut Vec>, i: usize, c: usize) -> i32 { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if i == 0 || c == 0 { + return 0; + } + // 既に記録があればそのまま返す + if mem[i][c] != -1 { + return mem[i][c]; + } + // ナップサック容量を超える場合は、入れない選択しかできない + if wgt[i - 1] > c as i32 { + return knapsack_dfs_mem(wgt, val, mem, i - 1, c); + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + let no = knapsack_dfs_mem(wgt, val, mem, i - 1, c); + let yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1] as usize) + val[i - 1]; + // 2 つの案のうち価値が大きい方を記録して返す + mem[i][c] = std::cmp::max(no, yes); + mem[i][c] +} + +/* 0-1 ナップサック:動的計画法 */ +fn knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { + let n = wgt.len(); + // dp テーブルを初期化 + let mut dp = vec![vec![0; cap + 1]; n + 1]; + // 状態遷移 + for i in 1..=n { + for c in 1..=cap { + if wgt[i - 1] > c as i32 { + // ナップサック容量を超えるなら品物 i は選ばない + dp[i][c] = dp[i - 1][c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i][c] = std::cmp::max( + dp[i - 1][c], + dp[i - 1][c - wgt[i - 1] as usize] + val[i - 1], + ); + } + } + } + dp[n][cap] +} + +/* 0-1 ナップサック:空間最適化後の動的計画法 */ +fn knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { + let n = wgt.len(); + // dp テーブルを初期化 + let mut dp = vec![0; cap + 1]; + // 状態遷移 + for i in 1..=n { + // 逆順に走査する + for c in (1..=cap).rev() { + if wgt[i - 1] <= c as i32 { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); + } + } + } + dp[cap] +} + +/* Driver Code */ +pub fn main() { + let wgt = [10, 20, 30, 40, 50]; + let val = [50, 120, 150, 210, 240]; + let cap: usize = 50; + let n = wgt.len(); + + // 全探索 + let res = knapsack_dfs(&wgt, &val, n, cap); + println!("ナップサック容量を超えない最大価値は {res}"); + + // メモ化探索 + let mut mem = vec![vec![0; cap + 1]; n + 1]; + for row in mem.iter_mut() { + row.fill(-1); + } + let res = knapsack_dfs_mem(&wgt, &val, &mut mem, n, cap); + println!("ナップサック容量を超えない最大価値は {res}"); + + // 動的計画法 + let res = knapsack_dp(&wgt, &val, cap); + println!("ナップサック容量を超えない最大価値は {res}"); + + // 空間最適化後の動的計画法 + let res = knapsack_dp_comp(&wgt, &val, cap); + println!("ナップサック容量を超えない最大価値は {res}"); +} diff --git a/ja/codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs b/ja/codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs new file mode 100644 index 000000000..57bbeef3d --- /dev/null +++ b/ja/codes/rust/chapter_dynamic_programming/min_cost_climbing_stairs_dp.rs @@ -0,0 +1,52 @@ +/* + * File: min_cost_climbing_stairs_dp.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +use std::cmp; + +/* 階段登りの最小コスト:動的計画法 */ +fn min_cost_climbing_stairs_dp(cost: &[i32]) -> i32 { + let n = cost.len() - 1; + if n == 1 || n == 2 { + return cost[n]; + } + // 部分問題の解を保存するために dp テーブルを初期化 + let mut dp = vec![-1; n + 1]; + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for i in 3..=n { + dp[i] = cmp::min(dp[i - 1], dp[i - 2]) + cost[i]; + } + dp[n] +} + +/* 階段昇りの最小コスト:空間最適化後の動的計画法 */ +fn min_cost_climbing_stairs_dp_comp(cost: &[i32]) -> i32 { + let n = cost.len() - 1; + if n == 1 || n == 2 { + return cost[n]; + }; + let (mut a, mut b) = (cost[1], cost[2]); + for i in 3..=n { + let tmp = b; + b = cmp::min(a, tmp) + cost[i]; + a = tmp; + } + b +} + +/* Driver Code */ +pub fn main() { + let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; + println!("入力された階段コストのリストは {:?}", &cost); + + let res = min_cost_climbing_stairs_dp(&cost); + println!("階段を上り切る最小コストは {res}"); + + let res = min_cost_climbing_stairs_dp_comp(&cost); + println!("階段を上り切る最小コストは {res}"); +} diff --git a/ja/codes/rust/chapter_dynamic_programming/min_path_sum.rs b/ja/codes/rust/chapter_dynamic_programming/min_path_sum.rs new file mode 100644 index 000000000..390c85a06 --- /dev/null +++ b/ja/codes/rust/chapter_dynamic_programming/min_path_sum.rs @@ -0,0 +1,120 @@ +/* + * File: min_path_sum.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 最小経路和:全探索 */ +fn min_path_sum_dfs(grid: &Vec>, i: i32, j: i32) -> i32 { + // 左上のセルなら探索を終了する + if i == 0 && j == 0 { + return grid[0][0]; + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if i < 0 || j < 0 { + return i32::MAX; + } + // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する + let up = min_path_sum_dfs(grid, i - 1, j); + let left = min_path_sum_dfs(grid, i, j - 1); + // 左上隅から (i, j) までの最小経路コストを返す + std::cmp::min(left, up) + grid[i as usize][j as usize] +} + +/* 最小経路和:メモ化探索 */ +fn min_path_sum_dfs_mem(grid: &Vec>, mem: &mut Vec>, i: i32, j: i32) -> i32 { + // 左上のセルなら探索を終了する + if i == 0 && j == 0 { + return grid[0][0]; + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if i < 0 || j < 0 { + return i32::MAX; + } + // 既に記録があればそのまま返す + if mem[i as usize][j as usize] != -1 { + return mem[i as usize][j as usize]; + } + // 左と上のセルからの最小経路コスト + let up = min_path_sum_dfs_mem(grid, mem, i - 1, j); + let left = min_path_sum_dfs_mem(grid, mem, i, j - 1); + // 左上から (i, j) までの最小経路コストを記録して返す + mem[i as usize][j as usize] = std::cmp::min(left, up) + grid[i as usize][j as usize]; + mem[i as usize][j as usize] +} + +/* 最小経路和:動的計画法 */ +fn min_path_sum_dp(grid: &Vec>) -> i32 { + let (n, m) = (grid.len(), grid[0].len()); + // dp テーブルを初期化 + let mut dp = vec![vec![0; m]; n]; + dp[0][0] = grid[0][0]; + // 状態遷移:先頭行 + for j in 1..m { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 状態遷移:先頭列 + for i in 1..n { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 状態遷移: 残りの行と列 + for i in 1..n { + for j in 1..m { + dp[i][j] = std::cmp::min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + dp[n - 1][m - 1] +} + +/* 最小経路和:空間最適化後の動的計画法 */ +fn min_path_sum_dp_comp(grid: &Vec>) -> i32 { + let (n, m) = (grid.len(), grid[0].len()); + // dp テーブルを初期化 + let mut dp = vec![0; m]; + // 状態遷移:先頭行 + dp[0] = grid[0][0]; + for j in 1..m { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状態遷移:残りの行 + for i in 1..n { + // 状態遷移:先頭列 + dp[0] = dp[0] + grid[i][0]; + // 状態遷移:残りの列 + for j in 1..m { + dp[j] = std::cmp::min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + dp[m - 1] +} + +/* Driver Code */ +pub fn main() { + let grid = vec![ + vec![1, 3, 1, 5], + vec![2, 2, 4, 2], + vec![5, 3, 2, 1], + vec![4, 3, 5, 2], + ]; + let (n, m) = (grid.len(), grid[0].len()); + + // 全探索 + let res = min_path_sum_dfs(&grid, n as i32 - 1, m as i32 - 1); + println!("左上から右下までの最小経路和は {res}"); + + // メモ化探索 + let mut mem = vec![vec![0; m]; n]; + for row in mem.iter_mut() { + row.fill(-1); + } + let res = min_path_sum_dfs_mem(&grid, &mut mem, n as i32 - 1, m as i32 - 1); + println!("左上から右下までの最小経路和は {res}"); + + // 動的計画法 + let res = min_path_sum_dp(&grid); + println!("左上から右下までの最小経路和は {res}"); + + // 空間最適化後の動的計画法 + let res = min_path_sum_dp_comp(&grid); + println!("左上から右下までの最小経路和は {res}"); +} diff --git a/ja/codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs b/ja/codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs new file mode 100644 index 000000000..ac22ac39b --- /dev/null +++ b/ja/codes/rust/chapter_dynamic_programming/unbounded_knapsack.rs @@ -0,0 +1,60 @@ +/* + * File: unbounded_knapsack.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 完全ナップサック問題:動的計画法 */ +fn unbounded_knapsack_dp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { + let n = wgt.len(); + // dp テーブルを初期化 + let mut dp = vec![vec![0; cap + 1]; n + 1]; + // 状態遷移 + for i in 1..=n { + for c in 1..=cap { + if wgt[i - 1] > c as i32 { + // ナップサック容量を超えるなら品物 i は選ばない + dp[i][c] = dp[i - 1][c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i][c] = std::cmp::max(dp[i - 1][c], dp[i][c - wgt[i - 1] as usize] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +/* 完全ナップサック問題:空間最適化後の動的計画法 */ +fn unbounded_knapsack_dp_comp(wgt: &[i32], val: &[i32], cap: usize) -> i32 { + let n = wgt.len(); + // dp テーブルを初期化 + let mut dp = vec![0; cap + 1]; + // 状態遷移 + for i in 1..=n { + for c in 1..=cap { + if wgt[i - 1] > c as i32 { + // ナップサック容量を超えるなら品物 i は選ばない + dp[c] = dp[c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = std::cmp::max(dp[c], dp[c - wgt[i - 1] as usize] + val[i - 1]); + } + } + } + dp[cap] +} + +/* Driver Code */ +pub fn main() { + let wgt = [1, 2, 3]; + let val = [5, 11, 15]; + let cap: usize = 4; + + // 動的計画法 + let res = unbounded_knapsack_dp(&wgt, &val, cap); + println!("ナップサック容量を超えない最大価値は {res}"); + + // 空間最適化後の動的計画法 + let res = unbounded_knapsack_dp_comp(&wgt, &val, cap); + println!("ナップサック容量を超えない最大価値は {res}"); +} diff --git a/ja/codes/rust/chapter_graph/graph_adjacency_list.rs b/ja/codes/rust/chapter_graph/graph_adjacency_list.rs new file mode 100644 index 000000000..85ffab767 --- /dev/null +++ b/ja/codes/rust/chapter_graph/graph_adjacency_list.rs @@ -0,0 +1,135 @@ +/* + * File: graph_adjacency_list.rs + * Created Time: 2023-07-12 + * Author: night-cruise (2586447362@qq.com) + */ + +pub use hello_algo_rust::include::{vals_to_vets, vets_to_vals, Vertex}; + +use std::collections::HashMap; + +/* 隣接リストに基づく無向グラフ型 */ +pub struct GraphAdjList { + // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 + pub adj_list: HashMap>, // maybe HashSet for value part is better? +} + +impl GraphAdjList { + /* コンストラクタ */ + pub fn new(edges: Vec<[Vertex; 2]>) -> Self { + let mut graph = GraphAdjList { + adj_list: HashMap::new(), + }; + // すべての頂点と辺を追加 + for edge in edges { + graph.add_vertex(edge[0]); + graph.add_vertex(edge[1]); + graph.add_edge(edge[0], edge[1]); + } + + graph + } + + /* 頂点数を取得 */ + #[allow(unused)] + pub fn size(&self) -> usize { + self.adj_list.len() + } + + /* 辺を追加 */ + pub fn add_edge(&mut self, vet1: Vertex, vet2: Vertex) { + if vet1 == vet2 { + panic!("value error"); + } + // 辺 vet1 - vet2 を追加 + self.adj_list.entry(vet1).or_default().push(vet2); + self.adj_list.entry(vet2).or_default().push(vet1); + } + + /* 辺を削除 */ + #[allow(unused)] + pub fn remove_edge(&mut self, vet1: Vertex, vet2: Vertex) { + if vet1 == vet2 { + panic!("value error"); + } + // 辺 vet1 - vet2 を削除 + self.adj_list + .entry(vet1) + .and_modify(|v| v.retain(|&e| e != vet2)); + self.adj_list + .entry(vet2) + .and_modify(|v| v.retain(|&e| e != vet1)); + } + + /* 頂点を追加 */ + pub fn add_vertex(&mut self, vet: Vertex) { + if self.adj_list.contains_key(&vet) { + return; + } + // 隣接リストに新しいリストを追加 + self.adj_list.insert(vet, vec![]); + } + + /* 頂点を削除 */ + #[allow(unused)] + pub fn remove_vertex(&mut self, vet: Vertex) { + // 隣接リストから頂点 vet に対応するリストを削除 + self.adj_list.remove(&vet); + // 他の頂点のリストを走査し、vet を含むすべての辺を削除 + for list in self.adj_list.values_mut() { + list.retain(|&v| v != vet); + } + } + + /* 隣接リストを出力 */ + pub fn print(&self) { + println!("隣接リスト ="); + for (vertex, list) in &self.adj_list { + let list = list.iter().map(|vertex| vertex.val).collect::>(); + println!("{}: {:?},", vertex.val, list); + } + } +} + +/* Driver Code */ +#[allow(unused)] +fn main() { + /* 無向グラフを初期化 */ + let v = vals_to_vets(vec![1, 3, 2, 5, 4]); + let edges = vec![ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[3]], + [v[2], v[4]], + [v[3], v[4]], + ]; + + let mut graph = GraphAdjList::new(edges); + println!("\n初期化後、グラフは"); + graph.print(); + + /* 辺を追加 */ + // 頂点 1, 2 は v[0], v[2] + graph.add_edge(v[0], v[2]); + println!("\n辺 1-2 を追加した後、グラフは"); + graph.print(); + + /* 辺を削除 */ + // 頂点 1, 3 は v[0], v[1] + graph.remove_edge(v[0], v[1]); + println!("\n辺 1-3 を削除した後、グラフは"); + graph.print(); + + /* 頂点を追加 */ + let v5 = Vertex { val: 6 }; + graph.add_vertex(v5); + println!("\n頂点 6 を追加した後、グラフは"); + graph.print(); + + /* 頂点を削除 */ + // 頂点 3 は v[1] + graph.remove_vertex(v[1]); + println!("\n頂点 3 を削除した後、グラフは"); + graph.print(); +} diff --git a/ja/codes/rust/chapter_graph/graph_adjacency_matrix.rs b/ja/codes/rust/chapter_graph/graph_adjacency_matrix.rs new file mode 100644 index 000000000..0b1c17c9b --- /dev/null +++ b/ja/codes/rust/chapter_graph/graph_adjacency_matrix.rs @@ -0,0 +1,136 @@ +/* + * File: graph_adjacency_matrix.rs + * Created Time: 2023-07-12 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 隣接行列に基づく無向グラフ型 */ +pub struct GraphAdjMat { + // 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す + pub vertices: Vec, + // 隣接行列。行・列のインデックスは「頂点インデックス」に対応 + pub adj_mat: Vec>, +} + +impl GraphAdjMat { + /* コンストラクタ */ + pub fn new(vertices: Vec, edges: Vec<[usize; 2]>) -> Self { + let mut graph = GraphAdjMat { + vertices: vec![], + adj_mat: vec![], + }; + // 頂点を追加 + for val in vertices { + graph.add_vertex(val); + } + // 辺を追加 + // 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する + for edge in edges { + graph.add_edge(edge[0], edge[1]) + } + + graph + } + + /* 頂点数を取得 */ + pub fn size(&self) -> usize { + self.vertices.len() + } + + /* 頂点を追加 */ + pub fn add_vertex(&mut self, val: i32) { + let n = self.size(); + // 頂点リストに新しい頂点の値を追加 + self.vertices.push(val); + // 隣接行列に 1 行追加 + self.adj_mat.push(vec![0; n]); + // 隣接行列に 1 列追加 + for row in self.adj_mat.iter_mut() { + row.push(0); + } + } + + /* 頂点を削除 */ + pub fn remove_vertex(&mut self, index: usize) { + if index >= self.size() { + panic!("index error") + } + // 頂点リストから index の頂点を削除する + self.vertices.remove(index); + // 隣接行列で index 行を削除する + self.adj_mat.remove(index); + // 隣接行列で index 列を削除する + for row in self.adj_mat.iter_mut() { + row.remove(index); + } + } + + /* 辺を追加 */ + pub fn add_edge(&mut self, i: usize, j: usize) { + // パラメータ i, j は vertices の要素インデックスに対応する + // 範囲外と同値の場合の処理 + if i >= self.size() || j >= self.size() || i == j { + panic!("index error") + } + // 無向グラフでは、隣接行列は主対角線に関して対称、すなわち (i, j) == (j, i) を満たす + self.adj_mat[i][j] = 1; + self.adj_mat[j][i] = 1; + } + + /* 辺を削除 */ + // 引数 i, j は vertices の要素インデックスに対応する + pub fn remove_edge(&mut self, i: usize, j: usize) { + // パラメータ i, j は vertices の要素インデックスに対応する + // 範囲外と同値の場合の処理 + if i >= self.size() || j >= self.size() || i == j { + panic!("index error") + } + self.adj_mat[i][j] = 0; + self.adj_mat[j][i] = 0; + } + + /* 隣接行列を出力 */ + pub fn print(&self) { + println!("頂点リスト = {:?}", self.vertices); + println!("隣接行列 ="); + println!("["); + for row in &self.adj_mat { + println!(" {:?},", row); + } + println!("]") + } +} + +/* Driver Code */ +fn main() { + /* 無向グラフを初期化 */ + // edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意 + let vertices = vec![1, 3, 2, 5, 4]; + let edges = vec![[0, 1], [0, 3], [1, 2], [2, 3], [2, 4], [3, 4]]; + let mut graph = GraphAdjMat::new(vertices, edges); + println!("\n初期化後、グラフは"); + graph.print(); + + /* 辺を追加 */ + // 頂点 1, 2 のインデックスはそれぞれ 0, 2 + graph.add_edge(0, 2); + println!("\n辺 1-2 を追加した後、グラフは"); + graph.print(); + + /* 辺を削除 */ + // 頂点 1, 3 のインデックスはそれぞれ 0, 1 + graph.remove_edge(0, 1); + println!("\n辺 1-3 を削除した後、グラフは"); + graph.print(); + + /* 頂点を追加 */ + graph.add_vertex(6); + println!("\n頂点 6 を追加した後、グラフは"); + graph.print(); + + /* 頂点を削除 */ + // 頂点 3 のインデックスは 1 + graph.remove_vertex(1); + println!("\n頂点 3 を削除した後、グラフは"); + graph.print(); +} diff --git a/ja/codes/rust/chapter_graph/graph_bfs.rs b/ja/codes/rust/chapter_graph/graph_bfs.rs new file mode 100644 index 000000000..c9b5b3d3c --- /dev/null +++ b/ja/codes/rust/chapter_graph/graph_bfs.rs @@ -0,0 +1,69 @@ +/* + * File: graph_bfs.rs + * Created Time: 2023-07-12 + * Author: night-cruise (2586447362@qq.com) + */ + +mod graph_adjacency_list; + +use graph_adjacency_list::GraphAdjList; +use graph_adjacency_list::{vals_to_vets, vets_to_vals, Vertex}; +use std::collections::{HashSet, VecDeque}; + +/* 幅優先探索 */ +// グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする +fn graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { + // 頂点の走査順序 + let mut res = vec![]; + // 訪問済み頂点を記録するためのハッシュ集合 + let mut visited = HashSet::new(); + visited.insert(start_vet); + // BFS の実装にキューを用いる + let mut que = VecDeque::new(); + que.push_back(start_vet); + // 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す + while let Some(vet) = que.pop_front() { + res.push(vet); // 訪問した頂点を記録 + + // この頂点のすべての隣接頂点を走査 + if let Some(adj_vets) = graph.adj_list.get(&vet) { + for &adj_vet in adj_vets { + if visited.contains(&adj_vet) { + continue; // 訪問済みの頂点をスキップ + } + que.push_back(adj_vet); // 未訪問の頂点のみをキューに追加 + visited.insert(adj_vet); // この頂点を訪問済みにする + } + } + } + // 頂点の走査順を返す + res +} + +/* Driver Code */ +fn main() { + /* 無向グラフを初期化 */ + let v = vals_to_vets(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + let edges = vec![ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[1], v[4]], + [v[2], v[5]], + [v[3], v[4]], + [v[3], v[6]], + [v[4], v[5]], + [v[4], v[7]], + [v[5], v[8]], + [v[6], v[7]], + [v[7], v[8]], + ]; + let graph = GraphAdjList::new(edges); + println!("\n初期化後、グラフは"); + graph.print(); + + /* 幅優先探索 */ + let res = graph_bfs(graph, v[0]); + println!("\n幅優先探索(BFS)の頂点順序は"); + println!("{:?}", vets_to_vals(res)); +} diff --git a/ja/codes/rust/chapter_graph/graph_dfs.rs b/ja/codes/rust/chapter_graph/graph_dfs.rs new file mode 100644 index 000000000..296817f92 --- /dev/null +++ b/ja/codes/rust/chapter_graph/graph_dfs.rs @@ -0,0 +1,61 @@ +/* + * File: graph_dfs.rs + * Created Time: 2023-07-12 + * Author: night-cruise (2586447362@qq.com) + */ + +mod graph_adjacency_list; + +use graph_adjacency_list::GraphAdjList; +use graph_adjacency_list::{vals_to_vets, vets_to_vals, Vertex}; +use std::collections::HashSet; + +/* 深さ優先走査の補助関数 */ +fn dfs(graph: &GraphAdjList, visited: &mut HashSet, res: &mut Vec, vet: Vertex) { + res.push(vet); // 訪問した頂点を記録 + visited.insert(vet); // この頂点を訪問済みにする + // この頂点のすべての隣接頂点を走査 + if let Some(adj_vets) = graph.adj_list.get(&vet) { + for &adj_vet in adj_vets { + if visited.contains(&adj_vet) { + continue; // 訪問済みの頂点をスキップ + } + // 隣接頂点を再帰的に訪問 + dfs(graph, visited, res, adj_vet); + } + } +} + +/* 深さ優先探索 */ +// グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする +fn graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> Vec { + // 頂点の走査順序 + let mut res = vec![]; + // 訪問済み頂点を記録するためのハッシュ集合 + let mut visited = HashSet::new(); + dfs(&graph, &mut visited, &mut res, start_vet); + + res +} + +/* Driver Code */ +fn main() { + /* 無向グラフを初期化 */ + let v = vals_to_vets(vec![0, 1, 2, 3, 4, 5, 6]); + let edges = vec![ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], + ]; + let graph = GraphAdjList::new(edges); + println!("\n初期化後、グラフは"); + graph.print(); + + /* 深さ優先探索 */ + let res = graph_dfs(graph, v[0]); + println!("\n深さ優先探索(DFS)の頂点順序は"); + println!("{:?}", vets_to_vals(res)); +} diff --git a/ja/codes/rust/chapter_greedy/coin_change_greedy.rs b/ja/codes/rust/chapter_greedy/coin_change_greedy.rs new file mode 100644 index 000000000..53a254066 --- /dev/null +++ b/ja/codes/rust/chapter_greedy/coin_change_greedy.rs @@ -0,0 +1,54 @@ +/* + * File: coin_change_greedy.rs + * Created Time: 2023-07-22 + * Author: night-cruise (2586447362@qq.com) + */ + +/* コイン交換:貪欲法 */ +fn coin_change_greedy(coins: &[i32], mut amt: i32) -> i32 { + // coins リストはソート済みと仮定する + let mut i = coins.len() - 1; + let mut count = 0; + // 残額がなくなるまで貪欲選択を繰り返す + while amt > 0 { + // 残額以下で最も近い硬貨を見つける + while i > 0 && coins[i] > amt { + i -= 1; + } + // coins[i] を選択する + amt -= coins[i]; + count += 1; + } + // 実行可能な解が見つからなければ -1 を返す + if amt == 0 { + count + } else { + -1 + } +} + +/* Driver Code */ +fn main() { + // 貪欲法:大域最適解を保証できる + let coins = [1, 5, 10, 20, 50, 100]; + let amt = 186; + let res = coin_change_greedy(&coins, amt); + println!("\ncoins = {:?}, amt = {}", coins, amt); + println!("{} にするために必要な最小硬貨枚数は {}", amt, res); + + // 貪欲法:大域最適解を保証できない + let coins = [1, 20, 50]; + let amt = 60; + let res = coin_change_greedy(&coins, amt); + println!("\ncoins = {:?}, amt = {}", coins, amt); + println!("{} にするために必要な最小硬貨枚数は {}", amt, res); + println!("実際に必要な最小枚数は 3、つまり 20 + 20 + 20"); + + // 貪欲法:大域最適解を保証できない + let coins = [1, 49, 50]; + let amt = 98; + let res = coin_change_greedy(&coins, amt); + println!("\ncoins = {:?}, amt = {}", coins, amt); + println!("{} にするために必要な最小硬貨枚数は {}", amt, res); + println!("実際に必要な最小枚数は 2、つまり 49 + 49"); +} diff --git a/ja/codes/rust/chapter_greedy/fractional_knapsack.rs b/ja/codes/rust/chapter_greedy/fractional_knapsack.rs new file mode 100644 index 000000000..3742663ae --- /dev/null +++ b/ja/codes/rust/chapter_greedy/fractional_knapsack.rs @@ -0,0 +1,59 @@ +/* + * File: coin_change_greedy.rs + * Created Time: 2023-07-22 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 品物 */ +struct Item { + w: i32, // 品物の重さ + v: i32, // 品物の価値 +} + +impl Item { + fn new(w: i32, v: i32) -> Self { + Self { w, v } + } +} + +/* 分数ナップサック:貪欲法 */ +fn fractional_knapsack(wgt: &[i32], val: &[i32], mut cap: i32) -> f64 { + // 重さと価値の 2 属性を持つ品物リストを作成 + let mut items = wgt + .iter() + .zip(val.iter()) + .map(|(&w, &v)| Item::new(w, v)) + .collect::>(); + // 単位価値 item.v / item.w の高い順にソートする + items.sort_by(|a, b| { + (b.v as f64 / b.w as f64) + .partial_cmp(&(a.v as f64 / a.w as f64)) + .unwrap() + }); + // 貪欲選択を繰り返す + let mut res = 0.0; + for item in &items { + if item.w <= cap { + // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる + res += item.v as f64; + cap -= item.w; + } else { + // 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる + res += item.v as f64 / item.w as f64 * cap as f64; + // 残り容量がないため、ループを抜ける + break; + } + } + res +} + +/* Driver Code */ +fn main() { + let wgt = [10, 20, 30, 40, 50]; + let val = [50, 120, 150, 210, 240]; + let cap = 50; + + // 貪欲法 + let res = fractional_knapsack(&wgt, &val, cap); + println!("ナップサック容量を超えない最大価値は {}", res); +} diff --git a/ja/codes/rust/chapter_greedy/max_capacity.rs b/ja/codes/rust/chapter_greedy/max_capacity.rs new file mode 100644 index 000000000..4f267bb59 --- /dev/null +++ b/ja/codes/rust/chapter_greedy/max_capacity.rs @@ -0,0 +1,36 @@ +/* + * File: coin_change_greedy.rs + * Created Time: 2023-07-22 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 最大容量:貪欲法 */ +fn max_capacity(ht: &[i32]) -> i32 { + // i, j を初期化し、それぞれ配列の両端に置く + let mut i = 0; + let mut j = ht.len() - 1; + // 初期の最大容量は 0 + let mut res = 0; + // 2 枚の板が出会うまで貪欲選択を繰り返す + while i < j { + // 最大容量を更新する + let cap = std::cmp::min(ht[i], ht[j]) * (j - i) as i32; + res = std::cmp::max(res, cap); + // 短い方を内側へ動かす + if ht[i] < ht[j] { + i += 1; + } else { + j -= 1; + } + } + res +} + +/* Driver Code */ +fn main() { + let ht = [3, 8, 5, 2, 7, 7, 3, 4]; + + // 貪欲法 + let res = max_capacity(&ht); + println!("最大容量は {}", res); +} diff --git a/ja/codes/rust/chapter_greedy/max_product_cutting.rs b/ja/codes/rust/chapter_greedy/max_product_cutting.rs new file mode 100644 index 000000000..e89c30951 --- /dev/null +++ b/ja/codes/rust/chapter_greedy/max_product_cutting.rs @@ -0,0 +1,35 @@ +/* + * File: coin_change_greedy.rs + * Created Time: 2023-07-22 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 最大切断積:貪欲法 */ +fn max_product_cutting(n: i32) -> i32 { + // n <= 3 のときは、必ず 1 を切り出す + if n <= 3 { + return 1 * (n - 1); + } + // 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする + let a = n / 3; + let b = n % 3; + if b == 1 { + // 余りが 1 のときは、1 * 3 を 2 * 2 に変える + 3_i32.pow(a as u32 - 1) * 2 * 2 + } else if b == 2 { + // 余りが 2 のときは、そのままにする + 3_i32.pow(a as u32) * 2 + } else { + // 余りが 0 のときは、そのままにする + 3_i32.pow(a as u32) + } +} + +/* Driver Code */ +fn main() { + let n = 58; + + // 貪欲法 + let res = max_product_cutting(n); + println!("最大分割積は {}", res); +} diff --git a/ja/codes/rust/chapter_hashing/array_hash_map.rs b/ja/codes/rust/chapter_hashing/array_hash_map.rs new file mode 100644 index 000000000..16490c428 --- /dev/null +++ b/ja/codes/rust/chapter_hashing/array_hash_map.rs @@ -0,0 +1,124 @@ +/** + * File: array_hash_map.rs + * Created Time: 2023-2-18 + * Author: xBLACICEx (xBLACKICEx@outlook.com) + */ + +/* キーと値の組 */ +#[derive(Debug, Clone, PartialEq)] +pub struct Pair { + pub key: i32, + pub val: String, +} +/* 配列ベースのハッシュテーブル */ +pub struct ArrayHashMap { + buckets: Vec>, +} + +impl ArrayHashMap { + pub fn new() -> ArrayHashMap { + // 100 個のバケットを含む配列を初期化 + Self { + buckets: vec![None; 100], + } + } + + /* ハッシュ関数 */ + fn hash_func(&self, key: i32) -> usize { + key as usize % 100 + } + + /* 検索操作 */ + pub fn get(&self, key: i32) -> Option<&String> { + let index = self.hash_func(key); + self.buckets[index].as_ref().map(|pair| &pair.val) + } + + /* 追加操作 */ + pub fn put(&mut self, key: i32, val: &str) { + let index = self.hash_func(key); + self.buckets[index] = Some(Pair { + key, + val: val.to_string(), + }); + } + + /* 削除操作 */ + pub fn remove(&mut self, key: i32) { + let index = self.hash_func(key); + // None に設定し、削除を表す + self.buckets[index] = None; + } + + /* すべてのキーと値のペアを取得 */ + pub fn entry_set(&self) -> Vec<&Pair> { + self.buckets + .iter() + .filter_map(|pair| pair.as_ref()) + .collect() + } + + /* すべてのキーを取得 */ + pub fn key_set(&self) -> Vec<&i32> { + self.buckets + .iter() + .filter_map(|pair| pair.as_ref().map(|pair| &pair.key)) + .collect() + } + + /* すべての値を取得 */ + pub fn value_set(&self) -> Vec<&String> { + self.buckets + .iter() + .filter_map(|pair| pair.as_ref().map(|pair| &pair.val)) + .collect() + } + + /* ハッシュテーブルを出力 */ + pub fn print(&self) { + for pair in self.entry_set() { + println!("{} -> {}", pair.key, pair.val); + } + } +} + +fn main() { + /* ハッシュテーブルを初期化 */ + let mut map = ArrayHashMap::new(); + /* 追加操作 */ + // ハッシュテーブルにキーと値の組(key, value)を追加 + map.put(12836, "シャオハー"); + map.put(15937, "シャオルオ"); + map.put(16750, "シャオスワン"); + map.put(13276, "シャオファー"); + map.put(10583, "シャオヤー"); + println!("\n追加後、ハッシュテーブルは\nKey -> Value"); + map.print(); + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + let name = map.get(15937).unwrap(); + println!("\n学籍番号 15937 を入力すると、名前 {} が見つかりました", name); + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + map.remove(10583); + println!("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value"); + map.print(); + + /* ハッシュテーブルを走査 */ + println!("\nキーと値のペア Key->Value を走査"); + for pair in map.entry_set() { + println!("{} -> {}", pair.key, pair.val); + } + + println!("\nキー Key のみを走査"); + for key in map.key_set() { + println!("{}", key); + } + + println!("\n値 Value のみを走査"); + for val in map.value_set() { + println!("{}", val); + } +} diff --git a/ja/codes/rust/chapter_hashing/build_in_hash.rs b/ja/codes/rust/chapter_hashing/build_in_hash.rs new file mode 100644 index 000000000..2237403e6 --- /dev/null +++ b/ja/codes/rust/chapter_hashing/build_in_hash.rs @@ -0,0 +1,49 @@ +/* + * File: build_in_hash.rs + * Created Time: 2023-7-6 + * Author: WSL0809 (wslzzy@outlook.com) + */ + +use hello_algo_rust::include::ListNode; + +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + +/* Driver Code */ +fn main() { + let num = 3; + let mut num_hasher = DefaultHasher::new(); + num.hash(&mut num_hasher); + let hash_num = num_hasher.finish(); + println!("整数 {} のハッシュ値は {}", num, hash_num); + + let bol = true; + let mut bol_hasher = DefaultHasher::new(); + bol.hash(&mut bol_hasher); + let hash_bol = bol_hasher.finish(); + println!("真偽値 {} のハッシュ値は {}", bol, hash_bol); + + let dec: f32 = 3.14159; + let mut dec_hasher = DefaultHasher::new(); + dec.to_bits().hash(&mut dec_hasher); + let hash_dec = dec_hasher.finish(); + println!("小数 {} のハッシュ値は {}", dec, hash_dec); + + let str = "Hello アルゴリズム"; + let mut str_hasher = DefaultHasher::new(); + str.hash(&mut str_hasher); + let hash_str = str_hasher.finish(); + println!("文字列 {} のハッシュ値は {}", str, hash_str); + + let arr = (&12836, &"シャオハー"); + let mut tup_hasher = DefaultHasher::new(); + arr.hash(&mut tup_hasher); + let hash_tup = tup_hasher.finish(); + println!("タプル {:?} のハッシュ値は {}", arr, hash_tup); + + let node = ListNode::new(42); + let mut hasher = DefaultHasher::new(); + node.borrow().val.hash(&mut hasher); + let hash = hasher.finish(); + println!("ノードオブジェクト {:?} のハッシュ値は{}", node, hash); +} diff --git a/ja/codes/rust/chapter_hashing/hash_map.rs b/ja/codes/rust/chapter_hashing/hash_map.rs new file mode 100644 index 000000000..47925f768 --- /dev/null +++ b/ja/codes/rust/chapter_hashing/hash_map.rs @@ -0,0 +1,48 @@ +/* + * File: hash_map.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +use std::collections::HashMap; + +/* Driver Code */ +pub fn main() { + // ハッシュテーブルを初期化 + let mut map = HashMap::new(); + + // 追加操作 + // ハッシュテーブルにキーと値の組 (key, value) を追加する + map.insert(12836, "シャオハー"); + map.insert(15937, "シャオロー"); + map.insert(16750, "シャオスワン"); + map.insert(13276, "シャオファー"); + map.insert(10583, "シャオヤー"); + println!("\n追加後、ハッシュテーブルは\nKey -> Value"); + print_util::print_hash_map(&map); + + // 検索操作 + // ハッシュテーブルにキー key を入力し、値 value を取得する + let name = map.get(&15937).copied().unwrap(); + println!("\n学籍番号 15937 を入力すると、名前 {name} が見つかります"); + + // 削除操作 + // ハッシュテーブルからキーと値の組 (key, value) を削除する + _ = map.remove(&10583); + println!("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value"); + print_util::print_hash_map(&map); + + // ハッシュテーブルを走査 + println!("\nキーと値のペア Key->Value を走査"); + print_util::print_hash_map(&map); + println!("\nキー Key のみを走査"); + for key in map.keys() { + println!("{key}"); + } + println!("\n値 value のみを走査"); + for value in map.values() { + println!("{value}"); + } +} diff --git a/ja/codes/rust/chapter_hashing/hash_map_chaining.rs b/ja/codes/rust/chapter_hashing/hash_map_chaining.rs new file mode 100644 index 000000000..34cbce0e9 --- /dev/null +++ b/ja/codes/rust/chapter_hashing/hash_map_chaining.rs @@ -0,0 +1,160 @@ +/* + * File: hash_map_chaining.rs + * Created Time: 2023-07-07 + * Author: WSL0809 (wslzzy@outlook.com) + */ + +#[derive(Clone)] +/* キーと値の組 */ +struct Pair { + key: i32, + val: String, +} + +/* チェイン法ハッシュテーブル */ +struct HashMapChaining { + size: usize, + capacity: usize, + load_thres: f32, + extend_ratio: usize, + buckets: Vec>, +} + +impl HashMapChaining { + /* コンストラクタ */ + fn new() -> Self { + Self { + size: 0, + capacity: 4, + load_thres: 2.0 / 3.0, + extend_ratio: 2, + buckets: vec![vec![]; 4], + } + } + + /* ハッシュ関数 */ + fn hash_func(&self, key: i32) -> usize { + key as usize % self.capacity + } + + /* 負荷率 */ + fn load_factor(&self) -> f32 { + self.size as f32 / self.capacity as f32 + } + + /* 削除操作 */ + fn remove(&mut self, key: i32) -> Option { + let index = self.hash_func(key); + + // バケットを走査してキーと値のペアを削除 + for (i, p) in self.buckets[index].iter_mut().enumerate() { + if p.key == key { + let pair = self.buckets[index].remove(i); + self.size -= 1; + return Some(pair.val); + } + } + + // key が見つからない場合は None を返す + None + } + + /* ハッシュテーブルを拡張 */ + fn extend(&mut self) { + // 元のハッシュテーブルを一時保存 + let buckets_tmp = std::mem::take(&mut self.buckets); + + // リサイズ後の新しいハッシュテーブルを初期化 + self.capacity *= self.extend_ratio; + self.buckets = vec![Vec::new(); self.capacity as usize]; + self.size = 0; + + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す + for bucket in buckets_tmp { + for pair in bucket { + self.put(pair.key, pair.val); + } + } + } + + /* ハッシュテーブルを出力 */ + fn print(&self) { + for bucket in &self.buckets { + let mut res = Vec::new(); + for pair in bucket { + res.push(format!("{} -> {}", pair.key, pair.val)); + } + println!("{:?}", res); + } + } + + /* 追加操作 */ + fn put(&mut self, key: i32, val: String) { + // 負荷率がしきい値を超えたら、リサイズを実行 + if self.load_factor() > self.load_thres { + self.extend(); + } + + let index = self.hash_func(key); + + // バケットを走査し、指定した key が見つかれば対応する val を更新して返す + for pair in self.buckets[index].iter_mut() { + if pair.key == key { + pair.val = val; + return; + } + } + + // その key が存在しなければ、キーと値のペアを末尾に追加 + let pair = Pair { key, val }; + self.buckets[index].push(pair); + self.size += 1; + } + + /* 検索操作 */ + fn get(&self, key: i32) -> Option<&str> { + let index = self.hash_func(key); + + // バケットを走査し、key が見つかれば対応する val を返す + for pair in self.buckets[index].iter() { + if pair.key == key { + return Some(&pair.val); + } + } + + // key が見つからない場合は None を返す + None + } +} + +/* Driver Code */ +pub fn main() { + /* ハッシュテーブルを初期化 */ + let mut map = HashMapChaining::new(); + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.put(12836, "シャオハー".to_string()); + map.put(15937, "シャオロー".to_string()); + map.put(16750, "シャオスワン".to_string()); + map.put(13276, "シャオファー".to_string()); + map.put(10583, "シャオヤー".to_string()); + println!("\n追加後、ハッシュテーブルは\nKey -> Value"); + map.print(); + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + println!( + "\n学籍番号 13276 を入力すると、名前 {} が見つかります", + match map.get(13276) { + Some(value) => value, + None => "Not a valid Key", + } + ); + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + map.remove(12836); + println!("\n12836 を削除すると、ハッシュテーブルは\nKey -> Value"); + map.print(); +} diff --git a/ja/codes/rust/chapter_hashing/hash_map_open_addressing.rs b/ja/codes/rust/chapter_hashing/hash_map_open_addressing.rs new file mode 100644 index 000000000..39041d4c6 --- /dev/null +++ b/ja/codes/rust/chapter_hashing/hash_map_open_addressing.rs @@ -0,0 +1,181 @@ +/* + * File: hash_map_open_addressing.rs + * Created Time: 2023-07-16 + * Author: WSL0809 (wslzzy@outlook.com), night-cruise (2586447362@qq.com) + */ +#![allow(non_snake_case)] +#![allow(unused)] + +mod array_hash_map; + +use array_hash_map::Pair; + +/* オープンアドレス法ハッシュテーブル */ +struct HashMapOpenAddressing { + size: usize, // キーと値のペア数 + capacity: usize, // ハッシュテーブル容量 + load_thres: f64, // リサイズを発動する負荷率のしきい値 + extend_ratio: usize, // 拡張倍率 + buckets: Vec>, // バケット配列 + TOMBSTONE: Option, // 削除済みマーク +} + +impl HashMapOpenAddressing { + /* コンストラクタ */ + fn new() -> Self { + Self { + size: 0, + capacity: 4, + load_thres: 2.0 / 3.0, + extend_ratio: 2, + buckets: vec![None; 4], + TOMBSTONE: Some(Pair { + key: -1, + val: "-1".to_string(), + }), + } + } + + /* ハッシュ関数 */ + fn hash_func(&self, key: i32) -> usize { + (key % self.capacity as i32) as usize + } + + /* 負荷率 */ + fn load_factor(&self) -> f64 { + self.size as f64 / self.capacity as f64 + } + + /* key に対応するバケットインデックスを探す */ + fn find_bucket(&mut self, key: i32) -> usize { + let mut index = self.hash_func(key); + let mut first_tombstone = -1; + // 線形プロービングを行い、空バケットに達したら終了 + while self.buckets[index].is_some() { + // `key` に遭遇したら、対応するバケットのインデックスを返す + if self.buckets[index].as_ref().unwrap().key == key { + // 以前に削除マークに遭遇していた場合は、キーと値のペアをそのインデックスへ移動する + if first_tombstone != -1 { + self.buckets[first_tombstone as usize] = self.buckets[index].take(); + self.buckets[index] = self.TOMBSTONE.clone(); + return first_tombstone as usize; // 移動後のバケットインデックスを返す + } + return index; // バケットのインデックスを返す + } + // 最初に見つかった削除マークを記録 + if first_tombstone == -1 && self.buckets[index] == self.TOMBSTONE { + first_tombstone = index as i32; + } + // バケットのインデックスを計算し、末尾を越えたら先頭に戻る + index = (index + 1) % self.capacity; + } + // key が存在しない場合は追加位置のインデックスを返す + if first_tombstone == -1 { + index + } else { + first_tombstone as usize + } + } + + /* 検索操作 */ + fn get(&mut self, key: i32) -> Option<&str> { + // key に対応するバケットインデックスを探す + let index = self.find_bucket(key); + // キーと値の組が見つかったら、対応する val を返す + if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { + return self.buckets[index].as_ref().map(|pair| &pair.val as &str); + } + // キーと値の組が存在しなければ null を返す + None + } + + /* 追加操作 */ + fn put(&mut self, key: i32, val: String) { + // 負荷率がしきい値を超えたら、リサイズを実行 + if self.load_factor() > self.load_thres { + self.extend(); + } + // key に対応するバケットインデックスを探す + let index = self.find_bucket(key); + // キーと値の組が見つかったら、val を上書きして返す + if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { + self.buckets[index].as_mut().unwrap().val = val; + return; + } + // キーと値の組が存在しない場合は、その組を追加する + self.buckets[index] = Some(Pair { key, val }); + self.size += 1; + } + + /* 削除操作 */ + fn remove(&mut self, key: i32) { + // key に対応するバケットインデックスを探す + let index = self.find_bucket(key); + // キーと値の組が見つかったら、削除マーカーで上書きする + if self.buckets[index].is_some() && self.buckets[index] != self.TOMBSTONE { + self.buckets[index] = self.TOMBSTONE.clone(); + self.size -= 1; + } + } + + /* ハッシュテーブルを拡張 */ + fn extend(&mut self) { + // 元のハッシュテーブルを一時保存 + let buckets_tmp = self.buckets.clone(); + // リサイズ後の新しいハッシュテーブルを初期化 + self.capacity *= self.extend_ratio; + self.buckets = vec![None; self.capacity]; + self.size = 0; + + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す + for pair in buckets_tmp { + if pair.is_none() || pair == self.TOMBSTONE { + continue; + } + let pair = pair.unwrap(); + + self.put(pair.key, pair.val); + } + } + /* ハッシュテーブルを出力 */ + fn print(&self) { + for pair in &self.buckets { + if pair.is_none() { + println!("null"); + } else if pair == &self.TOMBSTONE { + println!("TOMBSTONE"); + } else { + let pair = pair.as_ref().unwrap(); + println!("{} -> {}", pair.key, pair.val); + } + } + } +} + +/* Driver Code */ +fn main() { + /* ハッシュテーブルを初期化 */ + let mut hashmap = HashMapOpenAddressing::new(); + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + hashmap.put(12836, "シャオハー".to_string()); + hashmap.put(15937, "シャオロー".to_string()); + hashmap.put(16750, "シャオスワン".to_string()); + hashmap.put(13276, "シャオファー".to_string()); + hashmap.put(10583, "シャオヤー".to_string()); + + println!("\n追加後、ハッシュテーブルは\nKey -> Value"); + hashmap.print(); + + /* 検索操作 */ + // ハッシュテーブルにキー key を入力し、値 val を得る + let name = hashmap.get(13276).unwrap(); + println!("\n学籍番号 13276 を入力すると、名前 {} が見つかります", name); + + /* 削除操作 */ + // ハッシュテーブルからキーと値の組 (key, val) を削除 + hashmap.remove(16750); + println!("\n16750 を削除すると、ハッシュテーブルは\nKey -> Value"); + hashmap.print(); +} diff --git a/ja/codes/rust/chapter_hashing/simple_hash.rs b/ja/codes/rust/chapter_hashing/simple_hash.rs new file mode 100644 index 000000000..b5c425c56 --- /dev/null +++ b/ja/codes/rust/chapter_hashing/simple_hash.rs @@ -0,0 +1,70 @@ +/* + * File: simple_hash.rs + * Created Time: 2023-09-07 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 加算ハッシュ */ +fn add_hash(key: &str) -> i32 { + let mut hash = 0_i64; + const MODULUS: i64 = 1000000007; + + for c in key.chars() { + hash = (hash + c as i64) % MODULUS; + } + + hash as i32 +} + +/* 乗算ハッシュ */ +fn mul_hash(key: &str) -> i32 { + let mut hash = 0_i64; + const MODULUS: i64 = 1000000007; + + for c in key.chars() { + hash = (31 * hash + c as i64) % MODULUS; + } + + hash as i32 +} + +/* XOR ハッシュ */ +fn xor_hash(key: &str) -> i32 { + let mut hash = 0_i64; + const MODULUS: i64 = 1000000007; + + for c in key.chars() { + hash ^= c as i64; + } + + (hash & MODULUS) as i32 +} + +/* 回転ハッシュ */ +fn rot_hash(key: &str) -> i32 { + let mut hash = 0_i64; + const MODULUS: i64 = 1000000007; + + for c in key.chars() { + hash = ((hash << 4) ^ (hash >> 28) ^ c as i64) % MODULUS; + } + + hash as i32 +} + +/* Driver Code */ +fn main() { + let key = "Hello アルゴリズム"; + + let hash = add_hash(key); + println!("加算ハッシュ値は {hash}"); + + let hash = mul_hash(key); + println!("乗算ハッシュ値は {hash}"); + + let hash = xor_hash(key); + println!("XORハッシュ値は {hash}"); + + let hash = rot_hash(key); + println!("回転ハッシュ値は {hash}"); +} diff --git a/ja/codes/rust/chapter_heap/heap.rs b/ja/codes/rust/chapter_heap/heap.rs new file mode 100644 index 000000000..301594b18 --- /dev/null +++ b/ja/codes/rust/chapter_heap/heap.rs @@ -0,0 +1,71 @@ +/* + * File: heap.rs + * Created Time: 2023-07-16 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +use std::{cmp::Reverse, collections::BinaryHeap}; + +fn test_push_max(heap: &mut BinaryHeap, val: i32) { + heap.push(val); // 要素をヒープに追加 + println!("\n要素 {} をヒープに追加した後", val); + print_util::print_heap(heap.iter().map(|&val| val).collect()); +} + +fn test_pop_max(heap: &mut BinaryHeap) { + let val = heap.pop().unwrap(); + println!("\nヒープ先頭要素 {} を取り出した後", val); + print_util::print_heap(heap.iter().map(|&val| val).collect()); +} + +/* Driver Code */ +fn main() { + /* ヒープを初期化 */ + // 最小ヒープを初期化 + #[allow(unused_assignments)] + let mut min_heap = BinaryHeap::new(); + // Rust の BinaryHeap は最大ヒープであり、最小ヒープには通常 Reverse を使う + // 最大ヒープを初期化する + let mut max_heap = BinaryHeap::new(); + + println!("\n以下のテストケースは最大ヒープです"); + + /* 要素をヒープに追加 */ + test_push_max(&mut max_heap, 1); + test_push_max(&mut max_heap, 3); + test_push_max(&mut max_heap, 2); + test_push_max(&mut max_heap, 5); + test_push_max(&mut max_heap, 4); + + /* ヒープ頂点の要素を取得 */ + let peek = max_heap.peek().unwrap(); + println!("\nヒープ先頭要素は {}", peek); + + /* ヒープ頂点の要素を取り出す */ + test_pop_max(&mut max_heap); + test_pop_max(&mut max_heap); + test_pop_max(&mut max_heap); + test_pop_max(&mut max_heap); + test_pop_max(&mut max_heap); + + /* ヒープのサイズを取得 */ + let size = max_heap.len(); + println!("\nヒープ要素数は {}", size); + + /* ヒープが空かどうかを判定 */ + let is_empty = max_heap.is_empty(); + println!("\nヒープは空か {}", is_empty); + + /* リストを入力してヒープを構築 */ + // 時間計算量は O(n) であり、O(nlogn) ではない + min_heap = BinaryHeap::from( + vec![1, 3, 2, 5, 4] + .into_iter() + .map(|val| Reverse(val)) + .collect::>>(), + ); + println!("\nリストを入力して最小ヒープを構築した後"); + print_util::print_heap(min_heap.iter().map(|&val| val.0).collect()); +} diff --git a/ja/codes/rust/chapter_heap/my_heap.rs b/ja/codes/rust/chapter_heap/my_heap.rs new file mode 100644 index 000000000..61970f551 --- /dev/null +++ b/ja/codes/rust/chapter_heap/my_heap.rs @@ -0,0 +1,165 @@ +/* + * File: my_heap.rs + * Created Time: 2023-07-16 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +/* 最大ヒープ */ +struct MaxHeap { + // 配列ではなく vector を使うことで、拡張を考慮せずに済む + max_heap: Vec, +} + +impl MaxHeap { + /* コンストラクタ。入力リストに基づいてヒープを構築する */ + fn new(nums: Vec) -> Self { + // リスト要素をそのままヒープに追加 + let mut heap = MaxHeap { max_heap: nums }; + // 葉ノード以外のすべてのノードをヒープ化 + for i in (0..=Self::parent(heap.size() - 1)).rev() { + heap.sift_down(i); + } + heap + } + + /* 左子ノードのインデックスを取得 */ + fn left(i: usize) -> usize { + 2 * i + 1 + } + + /* 右子ノードのインデックスを取得 */ + fn right(i: usize) -> usize { + 2 * i + 2 + } + + /* 親ノードのインデックスを取得 */ + fn parent(i: usize) -> usize { + (i - 1) / 2 // 切り捨て除算 + } + + /* 要素を交換 */ + fn swap(&mut self, i: usize, j: usize) { + self.max_heap.swap(i, j); + } + + /* ヒープのサイズを取得 */ + fn size(&self) -> usize { + self.max_heap.len() + } + + /* ヒープが空かどうかを判定 */ + fn is_empty(&self) -> bool { + self.max_heap.is_empty() + } + + /* ヒープ先頭要素にアクセス */ + fn peek(&self) -> Option { + self.max_heap.first().copied() + } + + /* 要素をヒープに追加 */ + fn push(&mut self, val: i32) { + // ノードを追加 + self.max_heap.push(val); + // 下から上へヒープ化 + self.sift_up(self.size() - 1); + } + + /* ノード i から始めて、下から上へヒープ化 */ + fn sift_up(&mut self, mut i: usize) { + loop { + // ノード i はすでにヒープの先頭ノードなので、ヒープ化を終了する + if i == 0 { + break; + } + // ノード i の親ノードを取得 + let p = Self::parent(i); + // 「ノードの修復が不要」になったら、ヒープ化を終了 + if self.max_heap[i] <= self.max_heap[p] { + break; + } + // 2 つのノードを交換 + self.swap(i, p); + // ループで下から上へヒープ化 + i = p; + } + } + + /* 要素をヒープから取り出す */ + fn pop(&mut self) -> i32 { + // 空判定の処理 + if self.is_empty() { + panic!("index out of bounds"); + } + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + self.swap(0, self.size() - 1); + // ノードを削除 + let val = self.max_heap.pop().unwrap(); + // 上から下へヒープ化 + self.sift_down(0); + // ヒープ先頭要素を返す + val + } + + /* ノード i から始めて、上から下へヒープ化 */ + fn sift_down(&mut self, mut i: usize) { + loop { + // ノード i, l, r のうち値が最大のノードを ma とする + let (l, r, mut ma) = (Self::left(i), Self::right(i), i); + if l < self.size() && self.max_heap[l] > self.max_heap[ma] { + ma = l; + } + if r < self.size() && self.max_heap[r] > self.max_heap[ma] { + ma = r; + } + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + if ma == i { + break; + } + // 2 つのノードを交換 + self.swap(i, ma); + // ループで上から下へヒープ化 + i = ma; + } + } + + /* ヒープ(二分木)を出力 */ + fn print(&self) { + print_util::print_heap(self.max_heap.clone()); + } +} + +/* Driver Code */ +fn main() { + /* 最大ヒープを初期化 */ + let mut max_heap = MaxHeap::new(vec![9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + println!("\nリストを入力してヒープを構築した後"); + max_heap.print(); + + /* ヒープ頂点の要素を取得 */ + let peek = max_heap.peek(); + if let Some(peek) = peek { + println!("\nヒープ先頭要素は {}", peek); + } + + /* 要素をヒープに追加 */ + let val = 7; + max_heap.push(val); + println!("\n要素 {} をヒープに追加した後", val); + max_heap.print(); + + /* ヒープ頂点の要素を取り出す */ + let peek = max_heap.pop(); + println!("\nヒープ先頭要素 {} を取り出した後", peek); + max_heap.print(); + + /* ヒープのサイズを取得 */ + let size = max_heap.size(); + println!("\nヒープ要素数は {}", size); + + /* ヒープが空かどうかを判定 */ + let is_empty = max_heap.is_empty(); + println!("\nヒープは空か {}", is_empty); +} diff --git a/ja/codes/rust/chapter_heap/top_k.rs b/ja/codes/rust/chapter_heap/top_k.rs new file mode 100644 index 000000000..ff328ea7f --- /dev/null +++ b/ja/codes/rust/chapter_heap/top_k.rs @@ -0,0 +1,39 @@ +/* + * File: top_k.rs + * Created Time: 2023-07-16 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +use std::cmp::Reverse; +use std::collections::BinaryHeap; + +/* ヒープに基づいて配列中の最大の k 個の要素を探す */ +fn top_k_heap(nums: Vec, k: usize) -> BinaryHeap> { + // BinaryHeap は最大ヒープであり、Reverse で要素の順序を反転することで最小ヒープを実現する + let mut heap = BinaryHeap::>::new(); + // 配列の先頭 k 個の要素をヒープに追加 + for &num in nums.iter().take(k) { + heap.push(Reverse(num)); + } + // k+1 番目の要素から開始し、ヒープ長を k に保つ + for &num in nums.iter().skip(k) { + // 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する + if num > heap.peek().unwrap().0 { + heap.pop(); + heap.push(Reverse(num)); + } + } + heap +} + +/* Driver Code */ +fn main() { + let nums = vec![1, 7, 6, 3, 2]; + let k = 3; + + let res = top_k_heap(nums, k); + println!("最大の {} 個の要素は", k); + print_util::print_heap(res.into_iter().map(|item| item.0).collect()); +} diff --git a/ja/codes/rust/chapter_searching/binary_search.rs b/ja/codes/rust/chapter_searching/binary_search.rs new file mode 100644 index 000000000..ede5cd680 --- /dev/null +++ b/ja/codes/rust/chapter_searching/binary_search.rs @@ -0,0 +1,65 @@ +/* + * File: binary_search.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com) + */ + +/* 二分探索(両閉区間) */ +fn binary_search(nums: &[i32], target: i32) -> i32 { + // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す + let mut i = 0; + let mut j = nums.len() as i32 - 1; + // ループし、探索区間が空になったら終了する(i > j で空) + while i <= j { + let m = i + (j - i) / 2; // 中点インデックス m を計算 + if nums[m as usize] < target { + // この場合、target は区間 [m+1, j] にある + i = m + 1; + } else if nums[m as usize] > target { + // この場合、target は区間 [i, m-1] にある + j = m - 1; + } else { + // 目標要素が見つかったらそのインデックスを返す + return m; + } + } + // 目標要素が見つからなければ -1 を返す + return -1; +} + +/* 二分探索(左閉右開区間) */ +fn binary_search_lcro(nums: &[i32], target: i32) -> i32 { + // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す + let mut i = 0; + let mut j = nums.len() as i32; + // ループし、探索区間が空になったら終了する(i = j で空) + while i < j { + let m = i + (j - i) / 2; // 中点インデックス m を計算 + if nums[m as usize] < target { + // この場合、target は区間 [m+1, j) にある + i = m + 1; + } else if nums[m as usize] > target { + // この場合、target は区間 [i, m) にある + j = m; + } else { + // 目標要素が見つかったらそのインデックスを返す + return m; + } + } + // 目標要素が見つからなければ -1 を返す + return -1; +} + +/* Driver Code */ +pub fn main() { + let target = 6; + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + + // 二分探索(両閉区間) + let mut index = binary_search(&nums, target); + println!("目的の要素 6 のインデックス = {index}"); + + // 二分探索(左閉右開区間) + index = binary_search_lcro(&nums, target); + println!("目的の要素 6 のインデックス = {index}"); +} diff --git a/ja/codes/rust/chapter_searching/binary_search_edge.rs b/ja/codes/rust/chapter_searching/binary_search_edge.rs new file mode 100644 index 000000000..e758bc3b8 --- /dev/null +++ b/ja/codes/rust/chapter_searching/binary_search_edge.rs @@ -0,0 +1,50 @@ +/* + * File: binary_search_edge.rs + * Created Time: 2023-08-30 + * Author: night-cruise (2586447362@qq.com) + */ + +mod binary_search_insertion; + +use binary_search_insertion::binary_search_insertion; + +/* 最も左の target を二分探索 */ +fn binary_search_left_edge(nums: &[i32], target: i32) -> i32 { + // target の挿入位置を探すのと等価 + let i = binary_search_insertion(nums, target); + // target が見つからなければ、-1 を返す + if i == nums.len() as i32 || nums[i as usize] != target { + return -1; + } + // target が見つかったら、インデックス i を返す + i +} + +/* 最も右の target を二分探索 */ +fn binary_search_right_edge(nums: &[i32], target: i32) -> i32 { + // 最左の target + 1 を探す問題に変換する + let i = binary_search_insertion(nums, target + 1); + // j は最も右の target を指し、i は target より大きい最初の要素を指す + let j = i - 1; + // target が見つからなければ、-1 を返す + if j == -1 || nums[j as usize] != target { + return -1; + } + // target が見つかったら、インデックス j を返す + j +} + +/* Driver Code */ +fn main() { + // 重複要素を含む配列 + let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + println!("\n配列 nums = {:?}", nums); + + // 二分探索で左端と右端を探す + for target in [6, 7] { + let index = binary_search_left_edge(&nums, target); + println!("最も左にある要素 {} のインデックスは {}", target, index); + let index = binary_search_right_edge(&nums, target); + println!("最も右にある要素 {} のインデックスは {}", target, index); + } +} diff --git a/ja/codes/rust/chapter_searching/binary_search_insertion.rs b/ja/codes/rust/chapter_searching/binary_search_insertion.rs new file mode 100644 index 000000000..6839f1c55 --- /dev/null +++ b/ja/codes/rust/chapter_searching/binary_search_insertion.rs @@ -0,0 +1,61 @@ +/* + * File: binary_search_insertion.rs + * Created Time: 2023-08-30 + * Author: night-cruise (2586447362@qq.com) + */ +#![allow(unused)] + +/* 二分探索で挿入位置を探す(重複要素なし) */ +fn binary_search_insertion_simple(nums: &[i32], target: i32) -> i32 { + let (mut i, mut j) = (0, nums.len() as i32 - 1); // 両閉区間 [0, n-1] を初期化 + while i <= j { + let m = i + (j - i) / 2; // 中点インデックス m を計算 + if nums[m as usize] < target { + i = m + 1; // target は区間 [m+1, j] にある + } else if nums[m as usize] > target { + j = m - 1; // target は区間 [i, m-1] にある + } else { + return m; + } + } + // target が見つからなければ、挿入位置 i を返す + i +} + +/* 二分探索で挿入位置を探す(重複要素あり) */ +pub fn binary_search_insertion(nums: &[i32], target: i32) -> i32 { + let (mut i, mut j) = (0, nums.len() as i32 - 1); // 両閉区間 [0, n-1] を初期化 + while i <= j { + let m = i + (j - i) / 2; // 中点インデックス m を計算 + if nums[m as usize] < target { + i = m + 1; // target は区間 [m+1, j] にある + } else if nums[m as usize] > target { + j = m - 1; // target は区間 [i, m-1] にある + } else { + j = m - 1; // target より小さい最初の要素は区間 [i, m-1] にある + } + } + // 挿入位置 i を返す + i +} + +/* Driver Code */ +fn main() { + // 重複要素のない配列 + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + println!("\n配列 nums = {:?}", nums); + // 二分探索で挿入位置を探す + for target in [6, 9] { + let index = binary_search_insertion_simple(&nums, target); + println!("要素 {} の挿入位置のインデックスは {}", target, index); + } + + // 重複要素を含む配列 + let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; + println!("\n配列 nums = {:?}", nums); + // 二分探索で挿入位置を探す + for target in [2, 6, 20] { + let index = binary_search_insertion(&nums, target); + println!("要素 {} の挿入位置のインデックスは {}", target, index); + } +} diff --git a/ja/codes/rust/chapter_searching/hashing_search.rs b/ja/codes/rust/chapter_searching/hashing_search.rs new file mode 100644 index 000000000..3c67b078c --- /dev/null +++ b/ja/codes/rust/chapter_searching/hashing_search.rs @@ -0,0 +1,50 @@ +/* + * File: hashing_search.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::ListNode; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +/* ハッシュ探索(配列) */ +fn hashing_search_array<'a>(map: &'a HashMap, target: i32) -> Option<&'a usize> { + // ハッシュテーブルの key: 対象要素、value: インデックス + // ハッシュテーブルにその key がなければ None を返す + map.get(&target) +} + +/* ハッシュ探索(連結リスト) */ +fn hashing_search_linked_list( + map: &HashMap>>>, + target: i32, +) -> Option<&Rc>>> { + // ハッシュテーブルの key: 対象ノードの値、value: ノードオブジェクト + // ハッシュテーブルにその key がなければ None を返す + map.get(&target) +} + +/* Driver Code */ +pub fn main() { + let target = 3; + + /* ハッシュ探索(配列) */ + let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + // ハッシュテーブルを初期化 + let mut map = HashMap::new(); + for (i, num) in nums.iter().enumerate() { + map.insert(*num, i); // key: 要素、value: インデックス + } + let index = hashing_search_array(&map, target); + println!("対象要素 3 のインデックス = {}", index.unwrap()); + + /* ハッシュ探索(連結リスト) */ + let head = ListNode::arr_to_linked_list(&nums); + // ハッシュテーブルを初期化する + // let mut map1 = HashMap::new(); + let map1 = ListNode::linked_list_to_hashmap(head); + let node = hashing_search_linked_list(&map1, target); + println!("対象ノード値 3 に対応するノードオブジェクトは {:?}", node); +} diff --git a/ja/codes/rust/chapter_searching/linear_search.rs b/ja/codes/rust/chapter_searching/linear_search.rs new file mode 100644 index 000000000..5a53fc94c --- /dev/null +++ b/ja/codes/rust/chapter_searching/linear_search.rs @@ -0,0 +1,54 @@ +/* + * File: linear_search.rs + * Created Time: 2023-07-09 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::ListNode; +use std::cell::RefCell; +use std::rc::Rc; + +/* 線形探索(配列) */ +fn linear_search_array(nums: &[i32], target: i32) -> i32 { + // 配列を走査 + for (i, num) in nums.iter().enumerate() { + // 目標要素が見つかったらそのインデックスを返す + if num == &target { + return i as i32; + } + } + // 目標要素が見つからなければ -1 を返す + return -1; +} + +/* 線形探索(連結リスト) */ +fn linear_search_linked_list( + head: Rc>>, + target: i32, +) -> Option>>> { + // 対象ノードが見つかったら、それを返す + if head.borrow().val == target { + return Some(head); + }; + // 対象ノードが見つかったら、それを返す + if let Some(node) = &head.borrow_mut().next { + return linear_search_linked_list(node.clone(), target); + } + // 対象ノードが見つからない場合は None を返す + return None; +} + +/* Driver Code */ +pub fn main() { + let target = 3; + + /* 配列で線形探索を行う */ + let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; + let index = linear_search_array(&nums, target); + println!("対象要素 3 のインデックス = {}", index); + + /* 連結リストで線形探索を行う */ + let head = ListNode::arr_to_linked_list(&nums); + let node = linear_search_linked_list(head.unwrap(), target); + println!("対象ノード値 3 に対応するノードオブジェクトは {:?}", node); +} diff --git a/ja/codes/rust/chapter_searching/two_sum.rs b/ja/codes/rust/chapter_searching/two_sum.rs new file mode 100644 index 000000000..a667ea5ae --- /dev/null +++ b/ja/codes/rust/chapter_searching/two_sum.rs @@ -0,0 +1,52 @@ +/* + * File: two_sum.rs + * Created Time: 2023-01-14 + * Author: xBLACICEx (xBLACKICEx@outlook.com), codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; +use std::collections::HashMap; + +/* 方法 1:総当たり列挙 */ +pub fn two_sum_brute_force(nums: &Vec, target: i32) -> Option> { + let size = nums.len(); + // 2重ループのため、時間計算量は O(n^2) + for i in 0..size - 1 { + for j in i + 1..size { + if nums[i] + nums[j] == target { + return Some(vec![i as i32, j as i32]); + } + } + } + None +} + +/* 方法 2:補助ハッシュテーブル */ +pub fn two_sum_hash_table(nums: &Vec, target: i32) -> Option> { + // 補助ハッシュテーブルを使用し、空間計算量は O(n) + let mut dic = HashMap::new(); + // 単一ループで、時間計算量は O(n) + for (i, num) in nums.iter().enumerate() { + match dic.get(&(target - num)) { + Some(v) => return Some(vec![*v as i32, i as i32]), + None => dic.insert(num, i as i32), + }; + } + None +} + +fn main() { + // ======= Test Case ======= + let nums = vec![2, 7, 11, 15]; + let target = 13; + + // ====== Driver Code ====== + // 方法 1 + let res = two_sum_brute_force(&nums, target).unwrap(); + print!("方法1 res = "); + print_util::print_array(&res); + // 方法 2 + let res = two_sum_hash_table(&nums, target).unwrap(); + print!("\n方法2 res = "); + print_util::print_array(&res); +} diff --git a/ja/codes/rust/chapter_sorting/bubble_sort.rs b/ja/codes/rust/chapter_sorting/bubble_sort.rs new file mode 100644 index 000000000..2aec2dcce --- /dev/null +++ b/ja/codes/rust/chapter_sorting/bubble_sort.rs @@ -0,0 +1,53 @@ +/* + * File: bubble_sort.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +/* バブルソート */ +fn bubble_sort(nums: &mut [i32]) { + // 外側のループ:未ソート区間は [0, i] + for i in (1..nums.len()).rev() { + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for j in 0..i { + if nums[j] > nums[j + 1] { + // nums[j] と nums[j + 1] を交換 + nums.swap(j, j + 1); + } + } + } +} + +/* バブルソート(フラグ最適化) */ +fn bubble_sort_with_flag(nums: &mut [i32]) { + // 外側のループ:未ソート区間は [0, i] + for i in (1..nums.len()).rev() { + let mut flag = false; // フラグを初期化する + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for j in 0..i { + if nums[j] > nums[j + 1] { + // nums[j] と nums[j + 1] を交換 + nums.swap(j, j + 1); + flag = true; // 交換する要素を記録 + } + } + if !flag { + break; // このバブル処理で要素交換が一度もなければそのまま終了 + }; + } +} + +/* Driver Code */ +pub fn main() { + let mut nums = [4, 1, 3, 1, 5, 2]; + bubble_sort(&mut nums); + print!("バブルソート完了後 nums = "); + print_util::print_array(&nums); + + let mut nums1 = [4, 1, 3, 1, 5, 2]; + bubble_sort_with_flag(&mut nums1); + print!("\nバブルソート完了後 nums1 = "); + print_util::print_array(&nums1); +} diff --git a/ja/codes/rust/chapter_sorting/bucket_sort.rs b/ja/codes/rust/chapter_sorting/bucket_sort.rs new file mode 100644 index 000000000..62e8b7f8d --- /dev/null +++ b/ja/codes/rust/chapter_sorting/bucket_sort.rs @@ -0,0 +1,43 @@ +/* + * File: bucket_sort.rs + * Created Time: 2023-07-09 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +/* バケットソート */ +fn bucket_sort(nums: &mut [f64]) { + // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする + let k = nums.len() / 2; + let mut buckets = vec![vec![]; k]; + // 1. 配列要素を各バケットに振り分ける + for &num in nums.iter() { + // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する + let i = (num * k as f64) as usize; + // num をバケット i に追加 + buckets[i].push(num); + } + // 2. 各バケットをソートする + for bucket in &mut buckets { + // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい + bucket.sort_by(|a, b| a.partial_cmp(b).unwrap()); + } + // 3. バケットを走査して結果を結合 + let mut i = 0; + for bucket in buckets.iter() { + for &num in bucket.iter() { + nums[i] = num; + i += 1; + } + } +} + +/* Driver Code */ +fn main() { + // 入力データは範囲 [0, 1) の浮動小数点数とする + let mut nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; + bucket_sort(&mut nums); + print!("バケットソート完了後 nums = "); + print_util::print_array(&nums); +} diff --git a/ja/codes/rust/chapter_sorting/counting_sort.rs b/ja/codes/rust/chapter_sorting/counting_sort.rs new file mode 100644 index 000000000..c9740fb0f --- /dev/null +++ b/ja/codes/rust/chapter_sorting/counting_sort.rs @@ -0,0 +1,70 @@ +/* + * File: counting_sort.rs + * Created Time: 2023-07-09 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +/* 計数ソート */ +// 簡易実装のため、オブジェクトのソートには使えない +fn counting_sort_naive(nums: &mut [i32]) { + // 1. 配列の最大要素 m を求める + let m = *nums.iter().max().unwrap(); + // 2. 各数値の出現回数を数える + // counter[num] は num の出現回数を表す + let mut counter = vec![0; m as usize + 1]; + for &num in nums.iter() { + counter[num as usize] += 1; + } + // 3. counter を走査し、各要素を元の配列 nums に書き戻す + let mut i = 0; + for num in 0..m + 1 { + for _ in 0..counter[num as usize] { + nums[i] = num; + i += 1; + } + } +} + +/* 計数ソート */ +// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである +fn counting_sort(nums: &mut [i32]) { + // 1. 配列の最大要素 m を求める + let m = *nums.iter().max().unwrap() as usize; + // 2. 各数値の出現回数を数える + // counter[num] は num の出現回数を表す + let mut counter = vec![0; m + 1]; + for &num in nums.iter() { + counter[num as usize] += 1; + } + // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する + // つまり counter[num]-1 は、num が res に最後に現れるインデックス + for i in 0..m { + counter[i + 1] += counter[i]; + } + // 4. nums を逆順に走査し、各要素を結果配列 res に格納する + // 結果を記録するための配列 res を初期化 + let n = nums.len(); + let mut res = vec![0; n]; + for i in (0..n).rev() { + let num = nums[i]; + res[counter[num as usize] - 1] = num; // num を対応するインデックスに配置 + counter[num as usize] -= 1; // 累積和を 1 減らして、次に num を配置するインデックスを得る + } + // 結果配列 res で元の配列 nums を上書きする + nums.copy_from_slice(&res) +} + +/* Driver Code */ +fn main() { + let mut nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + counting_sort_naive(&mut nums); + print!("カウントソート(オブジェクトはソート不可)完了後 nums = "); + print_util::print_array(&nums); + + let mut nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; + counting_sort(&mut nums1); + print!("\nカウントソート完了後 nums1 = "); + print_util::print_array(&nums1); +} diff --git a/ja/codes/rust/chapter_sorting/heap_sort.rs b/ja/codes/rust/chapter_sorting/heap_sort.rs new file mode 100644 index 000000000..168159e21 --- /dev/null +++ b/ja/codes/rust/chapter_sorting/heap_sort.rs @@ -0,0 +1,54 @@ +/* + * File: heap_sort.rs + * Created Time: 2023-07-04 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +/* ヒープの長さは n。ノード i から下方向にヒープ化 */ +fn sift_down(nums: &mut [i32], n: usize, mut i: usize) { + loop { + // ノード i, l, r のうち値が最大のノードを ma とする + let l = 2 * i + 1; + let r = 2 * i + 2; + let mut ma = i; + if l < n && nums[l] > nums[ma] { + ma = l; + } + if r < n && nums[r] > nums[ma] { + ma = r; + } + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + if ma == i { + break; + } + // 2 つのノードを交換 + nums.swap(i, ma); + // ループで上から下へヒープ化 + i = ma; + } +} + +/* ヒープソート */ +fn heap_sort(nums: &mut [i32]) { + // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する + for i in (0..nums.len() / 2).rev() { + sift_down(nums, nums.len(), i); + } + // ヒープから最大要素を取り出し、n-1 回繰り返す + for i in (1..nums.len()).rev() { + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + nums.swap(0, i); + // 根ノードを起点に、上から下へヒープ化 + sift_down(nums, i, 0); + } +} + +/* Driver Code */ +fn main() { + let mut nums = [4, 1, 3, 1, 5, 2]; + heap_sort(&mut nums); + print!("ヒープソート完了後 nums = "); + print_util::print_array(&nums); +} diff --git a/ja/codes/rust/chapter_sorting/insertion_sort.rs b/ja/codes/rust/chapter_sorting/insertion_sort.rs new file mode 100644 index 000000000..c22649236 --- /dev/null +++ b/ja/codes/rust/chapter_sorting/insertion_sort.rs @@ -0,0 +1,29 @@ +/* + * File: insertion_sort.rs + * Created Time: 2023-02-13 + * Author: xBLACKICEx (xBLACKICEx@outlook.com) + */ + +use hello_algo_rust::include::print_util; + +/* 挿入ソート */ +fn insertion_sort(nums: &mut [i32]) { + // 外側ループ:整列済み区間は [0, i-1] + for i in 1..nums.len() { + let (base, mut j) = (nums[i], (i - 1) as i32); + // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する + while j >= 0 && nums[j as usize] > base { + nums[(j + 1) as usize] = nums[j as usize]; // nums[j] を 1 つ右へ移動する + j -= 1; + } + nums[(j + 1) as usize] = base; // base を正しい位置に配置する + } +} + +/* Driver Code */ +fn main() { + let mut nums = [4, 1, 3, 1, 5, 2]; + insertion_sort(&mut nums); + print!("挿入ソート完了後 nums = "); + print_util::print_array(&nums); +} diff --git a/ja/codes/rust/chapter_sorting/merge_sort.rs b/ja/codes/rust/chapter_sorting/merge_sort.rs new file mode 100644 index 000000000..b94ab5e19 --- /dev/null +++ b/ja/codes/rust/chapter_sorting/merge_sort.rs @@ -0,0 +1,66 @@ +/** + * File: merge_sort.rs + * Created Time: 2023-02-14 + * Author: xBLACKICEx (xBLACKICEx@outlook.com) + */ + +/* 左部分配列と右部分配列をマージ */ +fn merge(nums: &mut [i32], left: usize, mid: usize, right: usize) { + // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] + // マージ結果を格納する一時配列 tmp を作成 + let tmp_size = right - left + 1; + let mut tmp = vec![0; tmp_size]; + // 左右の部分配列の開始インデックスを初期化する + let (mut i, mut j, mut k) = (left, mid + 1, 0); + // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする + while i <= mid && j <= right { + if nums[i] <= nums[j] { + tmp[k] = nums[i]; + i += 1; + } else { + tmp[k] = nums[j]; + j += 1; + } + k += 1; + } + // 左右の部分配列の残り要素を一時配列にコピーする + while i <= mid { + tmp[k] = nums[i]; + k += 1; + i += 1; + } + while j <= right { + tmp[k] = nums[j]; + k += 1; + j += 1; + } + // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする + for k in 0..tmp_size { + nums[left + k] = tmp[k]; + } +} + +/* マージソート */ +fn merge_sort(nums: &mut [i32], left: usize, right: usize) { + // 終了条件 + if left >= right { + return; // 部分配列の長さが 1 になったら再帰を終了 + } + + // 分割フェーズ + let mid = left + (right - left) / 2; // 中点を計算 + merge_sort(nums, left, mid); // 左部分配列を再帰処理 + merge_sort(nums, mid + 1, right); // 右部分配列を再帰処理 + + // マージフェーズ + merge(nums, left, mid, right); +} + +/* Driver Code */ +fn main() { + /* マージソート */ + let mut nums = [7, 3, 2, 6, 0, 1, 5, 4]; + let right = nums.len() - 1; + merge_sort(&mut nums, 0, right); + println!("マージソート完了後 nums = {:?}", nums); +} diff --git a/ja/codes/rust/chapter_sorting/quick_sort.rs b/ja/codes/rust/chapter_sorting/quick_sort.rs new file mode 100644 index 000000000..b81d6cc21 --- /dev/null +++ b/ja/codes/rust/chapter_sorting/quick_sort.rs @@ -0,0 +1,148 @@ +/** + * File: quick_sort.rs + * Created Time: 2023-02-16 + * Author: xBLACKICEx (xBLACKICE@outlook.com) + */ + +/* クイックソート */ +struct QuickSort; + +impl QuickSort { + /* 番兵分割 */ + fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { + // nums[left] を基準値とする + let (mut i, mut j) = (left, right); + while i < j { + while i < j && nums[j] >= nums[left] { + j -= 1; // 右から左へ基準値未満の最初の要素を探す + } + while i < j && nums[i] <= nums[left] { + i += 1; // 左から右へ基準値より大きい最初の要素を探す + } + nums.swap(i, j); // この 2 つの要素を交換 + } + nums.swap(i, left); // 基準値を 2 つの部分配列の境界へ交換する + i // 基準値のインデックスを返す + } + + /* クイックソート */ + pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { + // 部分配列の長さが 1 なら再帰を終了する + if left >= right { + return; + } + // 番兵分割 + let pivot = Self::partition(nums, left as usize, right as usize) as i32; + // 左右の部分配列を再帰処理 + Self::quick_sort(left, pivot - 1, nums); + Self::quick_sort(pivot + 1, right, nums); + } +} + +/* クイックソート(中央値の基準値で最適化) */ +struct QuickSortMedian; + +impl QuickSortMedian { + /* 3つの候補要素の中央値を選ぶ */ + fn median_three(nums: &mut [i32], left: usize, mid: usize, right: usize) -> usize { + let (l, m, r) = (nums[left], nums[mid], nums[right]); + if (l <= m && m <= r) || (r <= m && m <= l) { + return mid; // m は l と r の間 + } + if (m <= l && l <= r) || (r <= l && l <= m) { + return left; // l は m と r の間 + } + right + } + + /* 番兵による分割処理(3 点中央値) */ + fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { + // 3つの候補要素の中央値を選ぶ + let med = Self::median_three(nums, left, (left + right) / 2, right); + // 中央値を配列の最左端に交換する + nums.swap(left, med); + // nums[left] を基準値とする + let (mut i, mut j) = (left, right); + while i < j { + while i < j && nums[j] >= nums[left] { + j -= 1; // 右から左へ基準値未満の最初の要素を探す + } + while i < j && nums[i] <= nums[left] { + i += 1; // 左から右へ基準値より大きい最初の要素を探す + } + nums.swap(i, j); // この 2 つの要素を交換 + } + nums.swap(i, left); // 基準値を 2 つの部分配列の境界へ交換する + i // 基準値のインデックスを返す + } + + /* クイックソート */ + pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) { + // 部分配列の長さが 1 なら再帰を終了する + if left >= right { + return; + } + // 番兵分割 + let pivot = Self::partition(nums, left as usize, right as usize) as i32; + // 左右の部分配列を再帰処理 + Self::quick_sort(left, pivot - 1, nums); + Self::quick_sort(pivot + 1, right, nums); + } +} + +/* クイックソート(再帰深度最適化) */ +struct QuickSortTailCall; + +impl QuickSortTailCall { + /* 番兵分割 */ + fn partition(nums: &mut [i32], left: usize, right: usize) -> usize { + // nums[left] を基準値とする + let (mut i, mut j) = (left, right); + while i < j { + while i < j && nums[j] >= nums[left] { + j -= 1; // 右から左へ基準値未満の最初の要素を探す + } + while i < j && nums[i] <= nums[left] { + i += 1; // 左から右へ基準値より大きい最初の要素を探す + } + nums.swap(i, j); // この 2 つの要素を交換 + } + nums.swap(i, left); // 基準値を 2 つの部分配列の境界へ交換する + i // 基準値のインデックスを返す + } + + /* クイックソート(再帰深度最適化) */ + pub fn quick_sort(mut left: i32, mut right: i32, nums: &mut [i32]) { + // 部分配列の長さが 1 なら終了 + while left < right { + // 番兵による分割処理 + let pivot = Self::partition(nums, left as usize, right as usize) as i32; + // 2 つの部分配列のうち短いほうにクイックソートを適用する + if pivot - left < right - pivot { + Self::quick_sort(left, pivot - 1, nums); // 左部分配列を再帰的にソート + left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right] + } else { + Self::quick_sort(pivot + 1, right, nums); // 右部分配列を再帰的にソート + right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1] + } + } + } +} + +/* Driver Code */ +fn main() { + /* クイックソート */ + let mut nums = [2, 4, 1, 0, 3, 5]; + QuickSort::quick_sort(0, (nums.len() - 1) as i32, &mut nums); + println!("クイックソート完了後 nums = {:?}", nums); + + /* クイックソート(中央値の基準値で最適化) */ + let mut nums = [2, 4, 1, 0, 3, 5]; + QuickSortMedian::quick_sort(0, (nums.len() - 1) as i32, &mut nums); + println!("クイックソート(中央値ピボット最適化)完了後 nums = {:?}", nums); + + /* クイックソート(再帰深度最適化) */ + let mut nums = [2, 4, 1, 0, 3, 5]; + QuickSortTailCall::quick_sort(0, (nums.len() - 1) as i32, &mut nums); + println!("クイックソート(再帰深度最適化)完了後 nums = {:?}", nums); +} diff --git a/ja/codes/rust/chapter_sorting/radix_sort.rs b/ja/codes/rust/chapter_sorting/radix_sort.rs new file mode 100644 index 000000000..0267b642e --- /dev/null +++ b/ja/codes/rust/chapter_sorting/radix_sort.rs @@ -0,0 +1,63 @@ +/* + * File: radix_sort.rs + * Created Time: 2023-07-09 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +/* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */ +fn digit(num: i32, exp: i32) -> usize { + // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す + return ((num / exp) % 10) as usize; +} + +/* 計数ソート(nums の k 桁目でソート) */ +fn counting_sort_digit(nums: &mut [i32], exp: i32) { + // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 + let mut counter = [0; 10]; + let n = nums.len(); + // 0~9 の各数字の出現回数を集計する + for i in 0..n { + let d = digit(nums[i], exp); // nums[i] の第 k 位を取得し、d とする + counter[d] += 1; // 数字 d の出現回数を数える + } + // 累積和を求め、「出現回数」を「配列インデックス」に変換する + for i in 1..10 { + counter[i] += counter[i - 1]; + } + // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する + let mut res = vec![0; n]; + for i in (0..n).rev() { + let d = digit(nums[i], exp); + let j = counter[d] - 1; // d の配列内インデックス j を取得する + res[j] = nums[i]; // 現在の要素をインデックス j に格納する + counter[d] -= 1; // d の個数を 1 減らす + } + // 結果で元の配列 nums を上書きする + nums.copy_from_slice(&res); +} + +/* 基数ソート */ +fn radix_sort(nums: &mut [i32]) { + // 最大桁数の判定用に配列の最大要素を取得 + let m = *nums.into_iter().max().unwrap(); + // 下位桁から上位桁の順に走査する + let mut exp = 1; + while exp <= m { + counting_sort_digit(nums, exp); + exp *= 10; + } +} + +/* Driver Code */ +fn main() { + // 基数ソート + let mut nums = [ + 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, 30524779, 82060337, + 63832996, + ]; + radix_sort(&mut nums); + print!("基数ソート完了後 nums = "); + print_util::print_array(&nums); +} diff --git a/ja/codes/rust/chapter_sorting/selection_sort.rs b/ja/codes/rust/chapter_sorting/selection_sort.rs new file mode 100644 index 000000000..70cd9fcd3 --- /dev/null +++ b/ja/codes/rust/chapter_sorting/selection_sort.rs @@ -0,0 +1,35 @@ +/* + * File: selection_sort.rs + * Created Time: 2023-05-30 + * Author: WSL0809 (wslzzy@outlook.com) + */ + +use hello_algo_rust::include::print_util; + +/* 選択ソート */ +fn selection_sort(nums: &mut [i32]) { + if nums.is_empty() { + return; + } + let n = nums.len(); + // 外側ループ:未整列区間は [i, n-1] + for i in 0..n - 1 { + // 内側のループ:未ソート区間の最小要素を見つける + let mut k = i; + for j in i + 1..n { + if nums[j] < nums[k] { + k = j; // 最小要素のインデックスを記録 + } + } + // その最小要素を未整列区間の先頭要素と交換する + nums.swap(i, k); + } +} + +/* Driver Code */ +pub fn main() { + let mut nums = [4, 1, 3, 1, 5, 2]; + selection_sort(&mut nums); + print!("\n選択ソート完了後 nums = "); + print_util::print_array(&nums); +} diff --git a/ja/codes/rust/chapter_stack_and_queue/array_deque.rs b/ja/codes/rust/chapter_stack_and_queue/array_deque.rs new file mode 100644 index 000000000..bfcdecbbf --- /dev/null +++ b/ja/codes/rust/chapter_stack_and_queue/array_deque.rs @@ -0,0 +1,160 @@ +/* + * File: array_deque.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ +use hello_algo_rust::include::print_util; +/* 循環配列ベースの両端キュー */ +struct ArrayDeque { + nums: Vec, // 両端キューの要素を格納する配列 + front: usize, // 先頭ポインタ。先頭要素を指す + que_size: usize, // 両端キューの長さ +} + +impl ArrayDeque { + /* コンストラクタ */ + pub fn new(capacity: usize) -> Self { + Self { + nums: vec![T::default(); capacity], + front: 0, + que_size: 0, + } + } + + /* 両端キューの容量を取得 */ + pub fn capacity(&self) -> usize { + self.nums.len() + } + + /* 両端キューの長さを取得 */ + pub fn size(&self) -> usize { + self.que_size + } + + /* 両端キューが空かどうかを判定 */ + pub fn is_empty(&self) -> bool { + self.que_size == 0 + } + + /* 循環配列のインデックスを計算 */ + fn index(&self, i: i32) -> usize { + // 剰余演算により配列の先頭と末尾をつなげる + // i が配列の末尾を越えたら先頭に戻る + // i が配列の先頭を越えて前に出たら末尾に戻る + ((i + self.capacity() as i32) % self.capacity() as i32) as usize + } + + /* キュー先頭にエンキュー */ + pub fn push_first(&mut self, num: T) { + if self.que_size == self.capacity() { + println!("両端キューがいっぱいです"); + return; + } + // 先頭ポインタを左に 1 つ移動する + // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする + self.front = self.index(self.front as i32 - 1); + // num をキュー先頭に追加 + self.nums[self.front] = num; + self.que_size += 1; + } + + /* キュー末尾にエンキュー */ + pub fn push_last(&mut self, num: T) { + if self.que_size == self.capacity() { + println!("両端キューがいっぱいです"); + return; + } + // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す + let rear = self.index(self.front as i32 + self.que_size as i32); + // num をキュー末尾に追加 + self.nums[rear] = num; + self.que_size += 1; + } + + /* キュー先頭からデキュー */ + fn pop_first(&mut self) -> T { + let num = self.peek_first(); + // 先頭ポインタを 1 つ後ろへ進める + self.front = self.index(self.front as i32 + 1); + self.que_size -= 1; + num + } + + /* キュー末尾からデキュー */ + fn pop_last(&mut self) -> T { + let num = self.peek_last(); + self.que_size -= 1; + num + } + + /* キュー先頭の要素にアクセス */ + fn peek_first(&self) -> T { + if self.is_empty() { + panic!("両端キューが空です") + }; + self.nums[self.front] + } + + /* キュー末尾の要素にアクセス */ + fn peek_last(&self) -> T { + if self.is_empty() { + panic!("両端キューが空です") + }; + // 末尾要素のインデックスを計算 + let last = self.index(self.front as i32 + self.que_size as i32 - 1); + self.nums[last] + } + + /* 出力用の配列を返す */ + fn to_array(&self) -> Vec { + // 有効長の範囲内のリスト要素のみを変換 + let mut res = vec![T::default(); self.que_size]; + let mut j = self.front; + for i in 0..self.que_size { + res[i] = self.nums[self.index(j as i32)]; + j += 1; + } + res + } +} + +/* Driver Code */ +fn main() { + /* 両端キューを初期化 */ + let mut deque = ArrayDeque::new(10); + deque.push_last(3); + deque.push_last(2); + deque.push_last(5); + print!("両端キュー deque = "); + print_util::print_array(&deque.to_array()); + + /* 要素にアクセス */ + let peek_first = deque.peek_first(); + print!("\n先頭要素 peek_first = {}", peek_first); + let peek_last = deque.peek_last(); + print!("\n末尾要素 peek_last = {}", peek_last); + + /* 要素をエンキュー */ + deque.push_last(4); + print!("\n要素 4 を末尾に追加後 deque = "); + print_util::print_array(&deque.to_array()); + deque.push_first(1); + print!("\n要素 1 を先頭に追加後 deque = "); + print_util::print_array(&deque.to_array()); + + /* 要素をデキュー */ + let pop_last = deque.pop_last(); + print!("\n末尾から取り出した要素 = {}、取り出し後 deque = ", pop_last); + print_util::print_array(&deque.to_array()); + let pop_first = deque.pop_first(); + print!("\n先頭から取り出した要素 = {}、取り出し後 deque = ", pop_first); + print_util::print_array(&deque.to_array()); + + /* 両端キューの長さを取得 */ + let size = deque.size(); + print!("\n両端キューの長さ size = {}", size); + + /* 両端キューが空かどうかを判定 */ + let is_empty = deque.is_empty(); + print!("\n両端キューが空かどうか = {}", is_empty); +} diff --git a/ja/codes/rust/chapter_stack_and_queue/array_queue.rs b/ja/codes/rust/chapter_stack_and_queue/array_queue.rs new file mode 100644 index 000000000..e5e55a235 --- /dev/null +++ b/ja/codes/rust/chapter_stack_and_queue/array_queue.rs @@ -0,0 +1,125 @@ +/* + * File: array_queue.rs + * Created Time: 2023-02-06 + * Author: WSL0809 (wslzzy@outlook.com) + */ + +/* 循環配列ベースのキュー */ +struct ArrayQueue { + nums: Vec, // キュー要素を格納する配列 + front: i32, // 先頭ポインタ。先頭要素を指す + que_size: i32, // キューの長さ + que_capacity: i32, // キューの容量 +} + +impl ArrayQueue { + /* コンストラクタ */ + fn new(capacity: i32) -> ArrayQueue { + ArrayQueue { + nums: vec![T::default(); capacity as usize], + front: 0, + que_size: 0, + que_capacity: capacity, + } + } + + /* キューの容量を取得 */ + fn capacity(&self) -> i32 { + self.que_capacity + } + + /* キューの長さを取得 */ + fn size(&self) -> i32 { + self.que_size + } + + /* キューが空かどうかを判定 */ + fn is_empty(&self) -> bool { + self.que_size == 0 + } + + /* エンキュー */ + fn push(&mut self, num: T) { + if self.que_size == self.capacity() { + println!("キューがいっぱいです"); + return; + } + // 末尾ポインタを計算し、末尾インデックス + 1 を指す + // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする + let rear = (self.front + self.que_size) % self.que_capacity; + // num をキュー末尾に追加 + self.nums[rear as usize] = num; + self.que_size += 1; + } + + /* デキュー */ + fn pop(&mut self) -> T { + let num = self.peek(); + // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す + self.front = (self.front + 1) % self.que_capacity; + self.que_size -= 1; + num + } + + /* キュー先頭の要素にアクセス */ + fn peek(&self) -> T { + if self.is_empty() { + panic!("index out of bounds"); + } + self.nums[self.front as usize] + } + + /* 配列を返す */ + fn to_vector(&self) -> Vec { + let cap = self.que_capacity; + let mut j = self.front; + let mut arr = vec![T::default(); cap as usize]; + for i in 0..self.que_size { + arr[i as usize] = self.nums[(j % cap) as usize]; + j += 1; + } + arr + } +} + +/* Driver Code */ +fn main() { + /* キューを初期化 */ + let capacity = 10; + let mut queue = ArrayQueue::new(capacity); + + /* 要素をエンキュー */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + println!("キュー queue = {:?}", queue.to_vector()); + + /* キュー先頭の要素にアクセス */ + let peek = queue.peek(); + println!("先頭要素 peek = {}", peek); + + /* 要素をデキュー */ + let pop = queue.pop(); + println!( + "取り出した要素 pop = {:?},取り出し後 queue = {:?}", + pop, + queue.to_vector() + ); + + /* キューの長さを取得 */ + let size = queue.size(); + println!("キューの長さ size = {}", size); + + /* キューが空かどうかを判定 */ + let is_empty = queue.is_empty(); + println!("キューが空かどうか = {}", is_empty); + + /* 循環配列をテストする */ + for i in 0..10 { + queue.push(i); + queue.pop(); + println!("第 {:?} 回のエンキュー + デキュー後 queue = {:?}", i, queue.to_vector()); + } +} diff --git a/ja/codes/rust/chapter_stack_and_queue/array_stack.rs b/ja/codes/rust/chapter_stack_and_queue/array_stack.rs new file mode 100644 index 000000000..67bbb6272 --- /dev/null +++ b/ja/codes/rust/chapter_stack_and_queue/array_stack.rs @@ -0,0 +1,86 @@ +/* + * File: array_stack.rs + * Created Time: 2023-02-05 + * Author: WSL0809 (wslzzy@outlook.com), codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +/* 配列ベースのスタック */ +struct ArrayStack { + stack: Vec, +} + +impl ArrayStack { + /* スタックを初期化 */ + fn new() -> ArrayStack { + ArrayStack:: { + stack: Vec::::new(), + } + } + + /* スタックの長さを取得 */ + fn size(&self) -> usize { + self.stack.len() + } + + /* スタックが空かどうかを判定 */ + fn is_empty(&self) -> bool { + self.size() == 0 + } + + /* プッシュ */ + fn push(&mut self, num: T) { + self.stack.push(num); + } + + /* ポップ */ + fn pop(&mut self) -> Option { + self.stack.pop() + } + + /* スタックトップの要素にアクセス */ + fn peek(&self) -> Option<&T> { + if self.is_empty() { + panic!("スタックが空です") + }; + self.stack.last() + } + + /* &Vec を返す */ + fn to_array(&self) -> &Vec { + &self.stack + } +} + +/* Driver Code */ +fn main() { + // スタックを初期化 + let mut stack = ArrayStack::::new(); + + // 要素をプッシュ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print!("スタック stack = "); + print_util::print_array(stack.to_array()); + + // スタックトップの要素にアクセス + let peek = stack.peek().unwrap(); + print!("\nスタックトップ要素 peek = {}", peek); + + // 要素をポップ + let pop = stack.pop().unwrap(); + print!("\n取り出した要素 pop = {pop}、取り出し後 stack = "); + print_util::print_array(stack.to_array()); + + // スタックの長さを取得 + let size = stack.size(); + print!("\nスタックの長さ size = {size}"); + + // 空かどうかを判定 + let is_empty = stack.is_empty(); + print!("\nスタックが空かどうか = {is_empty}"); +} diff --git a/ja/codes/rust/chapter_stack_and_queue/deque.rs b/ja/codes/rust/chapter_stack_and_queue/deque.rs new file mode 100644 index 000000000..6d0679382 --- /dev/null +++ b/ja/codes/rust/chapter_stack_and_queue/deque.rs @@ -0,0 +1,49 @@ +/* + * File: deque.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) + */ + +use hello_algo_rust::include::print_util; +use std::collections::VecDeque; + +/* Driver Code */ +pub fn main() { + // 両端キューを初期化 + let mut deque: VecDeque = VecDeque::new(); + deque.push_back(3); + deque.push_back(2); + deque.push_back(5); + print!("両端キュー deque = "); + print_util::print_queue(&deque); + + // 要素にアクセス + let peek_first = deque.front().unwrap(); + print!("\n先頭要素 peekFirst = {peek_first}"); + let peek_last = deque.back().unwrap(); + print!("\n末尾要素 peekLast = {peek_last}"); + + /* 要素をエンキュー */ + deque.push_back(4); + print!("\n要素 4 を末尾に追加後 deque = "); + print_util::print_queue(&deque); + deque.push_front(1); + print!("\n要素 1 を先頭に追加後 deque = "); + print_util::print_queue(&deque); + + // 要素をデキュー + let pop_last = deque.pop_back().unwrap(); + print!("\n末尾から取り出した要素 = {pop_last}、取り出し後 deque = "); + print_util::print_queue(&deque); + let pop_first = deque.pop_front().unwrap(); + print!("\n先頭から取り出した要素 = {pop_first}、取り出し後 deque = "); + print_util::print_queue(&deque); + + // 両端キューの長さを取得 + let size = deque.len(); + print!("\n両端キューの長さ size = {size}"); + + // 両端キューが空かどうかを判定 + let is_empty = deque.is_empty(); + print!("\n両端キューが空かどうか = {is_empty}"); +} diff --git a/ja/codes/rust/chapter_stack_and_queue/linkedlist_deque.rs b/ja/codes/rust/chapter_stack_and_queue/linkedlist_deque.rs new file mode 100644 index 000000000..bba56178b --- /dev/null +++ b/ja/codes/rust/chapter_stack_and_queue/linkedlist_deque.rs @@ -0,0 +1,218 @@ +/* + * File: linkedlist_deque.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +use std::cell::RefCell; +use std::rc::Rc; + +/* 双方向連結リストノード */ +pub struct ListNode { + pub val: T, // ノード値 + pub next: Option>>>, // 後継ノードへのポインタ + pub prev: Option>>>, // 前駆ノードへのポインタ +} + +impl ListNode { + pub fn new(val: T) -> Rc>> { + Rc::new(RefCell::new(ListNode { + val, + next: None, + prev: None, + })) + } +} + +/* 双方向連結リストベースの両端キュー */ +#[allow(dead_code)] +pub struct LinkedListDeque { + front: Option>>>, // 先頭ノード front + rear: Option>>>, // 末尾ノード rear + que_size: usize, // 両端キューの長さ +} + +impl LinkedListDeque { + pub fn new() -> Self { + Self { + front: None, + rear: None, + que_size: 0, + } + } + + /* 両端キューの長さを取得 */ + pub fn size(&self) -> usize { + return self.que_size; + } + + /* 両端キューが空かどうかを判定 */ + pub fn is_empty(&self) -> bool { + return self.que_size == 0; + } + + /* エンキュー操作 */ + fn push(&mut self, num: T, is_front: bool) { + let node = ListNode::new(num); + // 先頭へのエンキュー操作 + if is_front { + match self.front.take() { + // 連結リストが空なら、front と rear の両方を node に向ける + None => { + self.rear = Some(node.clone()); + self.front = Some(node); + } + // node を連結リストの先頭に追加 + Some(old_front) => { + old_front.borrow_mut().prev = Some(node.clone()); + node.borrow_mut().next = Some(old_front); + self.front = Some(node); // 先頭ノードを更新する + } + } + } + // 末尾へのエンキュー操作 + else { + match self.rear.take() { + // 連結リストが空なら、front と rear の両方を node に向ける + None => { + self.front = Some(node.clone()); + self.rear = Some(node); + } + // node を連結リストの末尾に追加 + Some(old_rear) => { + old_rear.borrow_mut().next = Some(node.clone()); + node.borrow_mut().prev = Some(old_rear); + self.rear = Some(node); // 末尾ノードを更新する + } + } + } + self.que_size += 1; // キューの長さを更新 + } + + /* キュー先頭にエンキュー */ + pub fn push_first(&mut self, num: T) { + self.push(num, true); + } + + /* キュー末尾にエンキュー */ + pub fn push_last(&mut self, num: T) { + self.push(num, false); + } + + /* デキュー操作 */ + fn pop(&mut self, is_front: bool) -> Option { + // キューが空なら、そのまま `None` を返す + if self.is_empty() { + return None; + }; + // キュー先頭からの取り出し + if is_front { + self.front.take().map(|old_front| { + match old_front.borrow_mut().next.take() { + Some(new_front) => { + new_front.borrow_mut().prev.take(); + self.front = Some(new_front); // 先頭ノードを更新する + } + None => { + self.rear.take(); + } + } + self.que_size -= 1; // キューの長さを更新 + old_front.borrow().val + }) + } + // キュー末尾からの取り出し + else { + self.rear.take().map(|old_rear| { + match old_rear.borrow_mut().prev.take() { + Some(new_rear) => { + new_rear.borrow_mut().next.take(); + self.rear = Some(new_rear); // 末尾ノードを更新する + } + None => { + self.front.take(); + } + } + self.que_size -= 1; // キューの長さを更新 + old_rear.borrow().val + }) + } + } + + /* キュー先頭からデキュー */ + pub fn pop_first(&mut self) -> Option { + return self.pop(true); + } + + /* キュー末尾からデキュー */ + pub fn pop_last(&mut self) -> Option { + return self.pop(false); + } + + /* キュー先頭の要素にアクセス */ + pub fn peek_first(&self) -> Option<&Rc>>> { + self.front.as_ref() + } + + /* キュー末尾の要素にアクセス */ + pub fn peek_last(&self) -> Option<&Rc>>> { + self.rear.as_ref() + } + + /* 出力用の配列を返す */ + pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { + let mut res: Vec = Vec::new(); + fn recur(cur: Option<&Rc>>>, res: &mut Vec) { + if let Some(cur) = cur { + res.push(cur.borrow().val); + recur(cur.borrow().next.as_ref(), res); + } + } + + recur(head, &mut res); + res + } +} + +/* Driver Code */ +fn main() { + /* 両端キューを初期化 */ + let mut deque = LinkedListDeque::new(); + deque.push_last(3); + deque.push_last(2); + deque.push_last(5); + print!("両端キュー deque = "); + print_util::print_array(&deque.to_array(deque.peek_first())); + + /* 要素にアクセス */ + let peek_first = deque.peek_first().unwrap().borrow().val; + print!("\n先頭要素 peek_first = {}", peek_first); + let peek_last = deque.peek_last().unwrap().borrow().val; + print!("\n末尾要素 peek_last = {}", peek_last); + + /* 要素をエンキュー */ + deque.push_last(4); + print!("\n要素 4 を末尾に追加後 deque = "); + print_util::print_array(&deque.to_array(deque.peek_first())); + deque.push_first(1); + print!("\n要素 1 を先頭に追加後 deque = "); + print_util::print_array(&deque.to_array(deque.peek_first())); + + /* 要素をデキュー */ + let pop_last = deque.pop_last().unwrap(); + print!("\n末尾から取り出した要素 = {}、取り出し後 deque = ", pop_last); + print_util::print_array(&deque.to_array(deque.peek_first())); + let pop_first = deque.pop_first().unwrap(); + print!("\n先頭から取り出した要素 = {}、取り出し後 deque = ", pop_first); + print_util::print_array(&deque.to_array(deque.peek_first())); + + /* 両端キューの長さを取得 */ + let size = deque.size(); + print!("\n両端キューの長さ size = {}", size); + + /* 両端キューが空かどうかを判定 */ + let is_empty = deque.is_empty(); + print!("\n両端キューが空かどうか = {}", is_empty); +} diff --git a/ja/codes/rust/chapter_stack_and_queue/linkedlist_queue.rs b/ja/codes/rust/chapter_stack_and_queue/linkedlist_queue.rs new file mode 100644 index 000000000..e77b434de --- /dev/null +++ b/ja/codes/rust/chapter_stack_and_queue/linkedlist_queue.rs @@ -0,0 +1,126 @@ +/* + * File: linkedlist_queue.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, ListNode}; + +use std::cell::RefCell; +use std::rc::Rc; + +/* 連結リストベースのキュー */ +#[allow(dead_code)] +pub struct LinkedListQueue { + front: Option>>>, // 先頭ノード front + rear: Option>>>, // 末尾ノード rear + que_size: usize, // キューの長さ +} + +impl LinkedListQueue { + pub fn new() -> Self { + Self { + front: None, + rear: None, + que_size: 0, + } + } + + /* キューの長さを取得 */ + pub fn size(&self) -> usize { + return self.que_size; + } + + /* キューが空かどうかを判定 */ + pub fn is_empty(&self) -> bool { + return self.que_size == 0; + } + + /* エンキュー */ + pub fn push(&mut self, num: T) { + // 末尾ノードの後ろに num を追加 + let new_rear = ListNode::new(num); + match self.rear.take() { + // キューが空でなければ、そのノードを末尾ノードの後ろに追加 + Some(old_rear) => { + old_rear.borrow_mut().next = Some(new_rear.clone()); + self.rear = Some(new_rear); + } + // キューが空なら、先頭・末尾ノードをともにそのノードに設定 + None => { + self.front = Some(new_rear.clone()); + self.rear = Some(new_rear); + } + } + self.que_size += 1; + } + + /* デキュー */ + pub fn pop(&mut self) -> Option { + self.front.take().map(|old_front| { + match old_front.borrow_mut().next.take() { + Some(new_front) => { + self.front = Some(new_front); + } + None => { + self.rear.take(); + } + } + self.que_size -= 1; + old_front.borrow().val + }) + } + + /* キュー先頭の要素にアクセス */ + pub fn peek(&self) -> Option<&Rc>>> { + self.front.as_ref() + } + + /* 連結リストを Array に変換して返す */ + pub fn to_array(&self, head: Option<&Rc>>>) -> Vec { + let mut res: Vec = Vec::new(); + + fn recur(cur: Option<&Rc>>>, res: &mut Vec) { + if let Some(cur) = cur { + res.push(cur.borrow().val); + recur(cur.borrow().next.as_ref(), res); + } + } + + recur(head, &mut res); + + res + } +} + +/* Driver Code */ +fn main() { + /* キューを初期化 */ + let mut queue = LinkedListQueue::new(); + + /* 要素をエンキュー */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + print!("キュー queue = "); + print_util::print_array(&queue.to_array(queue.peek())); + + /* キュー先頭の要素にアクセス */ + let peek = queue.peek().unwrap().borrow().val; + print!("\n先頭要素 peek = {}", peek); + + /* 要素をデキュー */ + let pop = queue.pop().unwrap(); + print!("\n取り出した要素 pop = {}、取り出し後 queue = ", pop); + print_util::print_array(&queue.to_array(queue.peek())); + + /* キューの長さを取得 */ + let size = queue.size(); + print!("\nキューの長さ size = {}", size); + + /* キューが空かどうかを判定 */ + let is_empty = queue.is_empty(); + print!("\nキューが空かどうか = {}", is_empty); +} diff --git a/ja/codes/rust/chapter_stack_and_queue/linkedlist_stack.rs b/ja/codes/rust/chapter_stack_and_queue/linkedlist_stack.rs new file mode 100644 index 000000000..6fd280902 --- /dev/null +++ b/ja/codes/rust/chapter_stack_and_queue/linkedlist_stack.rs @@ -0,0 +1,105 @@ +/* + * File: linkedlist_stack.rs + * Created Time: 2023-03-11 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::{print_util, ListNode}; + +use std::cell::RefCell; +use std::rc::Rc; + +/* 連結リストベースのスタック */ +#[allow(dead_code)] +pub struct LinkedListStack { + stack_peek: Option>>>, // 先頭ノードをスタックトップとする + stk_size: usize, // スタックの長さ +} + +impl LinkedListStack { + pub fn new() -> Self { + Self { + stack_peek: None, + stk_size: 0, + } + } + + /* スタックの長さを取得 */ + pub fn size(&self) -> usize { + return self.stk_size; + } + + /* スタックが空かどうかを判定 */ + pub fn is_empty(&self) -> bool { + return self.size() == 0; + } + + /* プッシュ */ + pub fn push(&mut self, num: T) { + let node = ListNode::new(num); + node.borrow_mut().next = self.stack_peek.take(); + self.stack_peek = Some(node); + self.stk_size += 1; + } + + /* ポップ */ + pub fn pop(&mut self) -> Option { + self.stack_peek.take().map(|old_head| { + self.stack_peek = old_head.borrow_mut().next.take(); + self.stk_size -= 1; + + old_head.borrow().val + }) + } + + /* スタックトップの要素にアクセス */ + pub fn peek(&self) -> Option<&Rc>>> { + self.stack_peek.as_ref() + } + + /* List を Array に変換して返す */ + pub fn to_array(&self) -> Vec { + fn _to_array(head: Option<&Rc>>>) -> Vec { + if let Some(node) = head { + let mut nums = _to_array(node.borrow().next.as_ref()); + nums.push(node.borrow().val); + return nums; + } + return Vec::new(); + } + + _to_array(self.peek()) + } +} + +/* Driver Code */ +fn main() { + /* スタックを初期化 */ + let mut stack = LinkedListStack::new(); + + /* 要素をプッシュ */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print!("スタック stack = "); + print_util::print_array(&stack.to_array()); + + /* スタックトップの要素にアクセス */ + let peek = stack.peek().unwrap().borrow().val; + print!("\nスタックトップ要素 peek = {}", peek); + + /* 要素をポップ */ + let pop = stack.pop().unwrap(); + print!("\n取り出した要素 pop = {}、取り出し後 stack = ", pop); + print_util::print_array(&stack.to_array()); + + /* スタックの長さを取得 */ + let size = stack.size(); + print!("\nスタックの長さ size = {}", size); + + /* 空かどうかを判定 */ + let is_empty = stack.is_empty(); + print!("\nスタックが空かどうか = {}", is_empty); +} diff --git a/ja/codes/rust/chapter_stack_and_queue/queue.rs b/ja/codes/rust/chapter_stack_and_queue/queue.rs new file mode 100644 index 000000000..35a823828 --- /dev/null +++ b/ja/codes/rust/chapter_stack_and_queue/queue.rs @@ -0,0 +1,41 @@ +/* + * File: queue.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) + */ + +use hello_algo_rust::include::print_util; + +use std::collections::VecDeque; + +/* Driver Code */ +pub fn main() { + // キューを初期化 + let mut queue: VecDeque = VecDeque::new(); + + // 要素をエンキュー + queue.push_back(1); + queue.push_back(3); + queue.push_back(2); + queue.push_back(5); + queue.push_back(4); + print!("キュー queue = "); + print_util::print_queue(&queue); + + // キュー先頭の要素にアクセス + let peek = queue.front().unwrap(); + println!("\n先頭要素 peek = {peek}"); + + // 要素をデキュー + let pop = queue.pop_front().unwrap(); + print!("取り出した要素 pop = {pop}、取り出し後 queue = "); + print_util::print_queue(&queue); + + // キューの長さを取得 + let size = queue.len(); + print!("\nキューの長さ size = {size}"); + + // キューが空かどうかを判定 + let is_empty = queue.is_empty(); + print!("\nキューが空かどうか = {is_empty}"); +} diff --git a/ja/codes/rust/chapter_stack_and_queue/stack.rs b/ja/codes/rust/chapter_stack_and_queue/stack.rs new file mode 100644 index 000000000..d89064098 --- /dev/null +++ b/ja/codes/rust/chapter_stack_and_queue/stack.rs @@ -0,0 +1,40 @@ +/* + * File: stack.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com) + */ + +use hello_algo_rust::include::print_util; + +/* Driver Code */ +pub fn main() { + // スタックを初期化する + // Rust では、Vec をスタックとして使うことが推奨される + let mut stack: Vec = Vec::new(); + + // 要素をプッシュ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + print!("スタック stack = "); + print_util::print_array(&stack); + + // スタックトップの要素にアクセス + let peek = stack.last().unwrap(); + print!("\nスタックトップ要素 peek = {peek}"); + + // 要素をポップ + let pop = stack.pop().unwrap(); + print!("\n取り出した要素 pop = {pop}、取り出し後 stack = "); + print_util::print_array(&stack); + + // スタックの長さを取得 + let size = stack.len(); + print!("\nスタックの長さ size = {size}"); + + // スタックが空かどうかを判定 + let is_empty = stack.is_empty(); + print!("\nスタックが空かどうか = {is_empty}"); +} diff --git a/ja/codes/rust/chapter_tree/array_binary_tree.rs b/ja/codes/rust/chapter_tree/array_binary_tree.rs new file mode 100644 index 000000000..2b7c57b08 --- /dev/null +++ b/ja/codes/rust/chapter_tree/array_binary_tree.rs @@ -0,0 +1,192 @@ +/* + * File: array_binary_tree.rs + * Created Time: 2023-07-25 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::{print_util, tree_node}; + +/* 配列表現による二分木クラス */ +struct ArrayBinaryTree { + tree: Vec>, +} + +impl ArrayBinaryTree { + /* コンストラクタ */ + fn new(arr: Vec>) -> Self { + Self { tree: arr } + } + + /* リスト容量 */ + fn size(&self) -> i32 { + self.tree.len() as i32 + } + + /* インデックス i のノードの値を取得 */ + fn val(&self, i: i32) -> Option { + // インデックスが範囲外なら、空きを表す None を返す + if i < 0 || i >= self.size() { + None + } else { + self.tree[i as usize] + } + } + + /* インデックス i のノードの左子ノードのインデックスを取得 */ + fn left(&self, i: i32) -> i32 { + 2 * i + 1 + } + + /* インデックス i のノードの右子ノードのインデックスを取得 */ + fn right(&self, i: i32) -> i32 { + 2 * i + 2 + } + + /* インデックス i のノードの親ノードのインデックスを取得 */ + fn parent(&self, i: i32) -> i32 { + (i - 1) / 2 + } + + /* レベル順走査 */ + fn level_order(&self) -> Vec { + self.tree.iter().filter_map(|&x| x).collect() + } + + /* 深さ優先探索 */ + fn dfs(&self, i: i32, order: &'static str, res: &mut Vec) { + if self.val(i).is_none() { + return; + } + let val = self.val(i).unwrap(); + // 先行順走査 + if order == "pre" { + res.push(val); + } + self.dfs(self.left(i), order, res); + // 中順走査 + if order == "in" { + res.push(val); + } + self.dfs(self.right(i), order, res); + // 後順走査 + if order == "post" { + res.push(val); + } + } + + /* 先行順走査 */ + fn pre_order(&self) -> Vec { + let mut res = vec![]; + self.dfs(0, "pre", &mut res); + res + } + + /* 中順走査 */ + fn in_order(&self) -> Vec { + let mut res = vec![]; + self.dfs(0, "in", &mut res); + res + } + + /* 後順走査 */ + fn post_order(&self) -> Vec { + let mut res = vec![]; + self.dfs(0, "post", &mut res); + res + } +} + +/* Driver Code */ +fn main() { + // 二分木を初期化 + // ここでは、配列から直接二分木を生成する関数を利用する + let arr = vec![ + Some(1), + Some(2), + Some(3), + Some(4), + None, + Some(6), + Some(7), + Some(8), + Some(9), + None, + None, + Some(12), + None, + None, + Some(15), + ]; + + let root = tree_node::vec_to_tree(arr.clone()).unwrap(); + println!("\n二分木を初期化\n"); + println!("二分木の配列表現:"); + println!( + "[{}]", + arr.iter() + .map(|&val| if let Some(val) = val { + format!("{val}") + } else { + "null".to_string() + }) + .collect::>() + .join(", ") + ); + println!("二分木の連結リスト表現:"); + print_util::print_tree(&root); + + // 配列表現による二分木クラス + let abt = ArrayBinaryTree::new(arr); + + // ノードにアクセス + let i = 1; + let l = abt.left(i); + let r = abt.right(i); + let p = abt.parent(i); + println!( + "\n現在のノードのインデックスは {}、値は {}", + i, + if let Some(val) = abt.val(i) { + format!("{val}") + } else { + "null".to_string() + } + ); + println!( + "その左子ノードのインデックスは {}、値は {}", + l, + if let Some(val) = abt.val(l) { + format!("{val}") + } else { + "null".to_string() + } + ); + println!( + "その右子ノードのインデックスは {}、値は {}", + r, + if let Some(val) = abt.val(r) { + format!("{val}") + } else { + "null".to_string() + } + ); + println!( + "その親ノードのインデックスは {}、値は {}", + p, + if let Some(val) = abt.val(p) { + format!("{val}") + } else { + "null".to_string() + } + ); + + // 木を走査 + let mut res = abt.level_order(); + println!("\nレベル順走査:{:?}", res); + res = abt.pre_order(); + println!("前順走査:{:?}", res); + res = abt.in_order(); + println!("中順走査:{:?}", res); + res = abt.post_order(); + println!("後順走査は:{:?}", res); +} diff --git a/ja/codes/rust/chapter_tree/avl_tree.rs b/ja/codes/rust/chapter_tree/avl_tree.rs new file mode 100644 index 000000000..3e665c067 --- /dev/null +++ b/ja/codes/rust/chapter_tree/avl_tree.rs @@ -0,0 +1,297 @@ +/* + * File: avl_tree.rs + * Created Time: 2023-07-14 + * Author: night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::{print_util, TreeNode}; + +use std::cell::RefCell; +use std::cmp::Ordering; +use std::rc::Rc; + +type OptionTreeNodeRc = Option>>; + +/* AVL 木 */ +struct AVLTree { + root: OptionTreeNodeRc, // 根ノード +} + +impl AVLTree { + /* コンストラクタ */ + fn new() -> Self { + Self { root: None } + } + + /* ノードの高さを取得 */ + fn height(node: OptionTreeNodeRc) -> i32 { + // 空ノードの高さは -1、葉ノードの高さは 0 + match node { + Some(node) => node.borrow().height, + None => -1, + } + } + + /* ノードの高さを更新する */ + fn update_height(node: OptionTreeNodeRc) { + if let Some(node) = node { + let left = node.borrow().left.clone(); + let right = node.borrow().right.clone(); + // ノードの高さは最も高い部分木の高さ + 1 に等しい + node.borrow_mut().height = std::cmp::max(Self::height(left), Self::height(right)) + 1; + } + } + + /* 平衡係数を取得 */ + fn balance_factor(node: OptionTreeNodeRc) -> i32 { + match node { + // 空ノードの平衡係数は 0 + None => 0, + // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ + Some(node) => { + Self::height(node.borrow().left.clone()) - Self::height(node.borrow().right.clone()) + } + } + } + + /* 右回転 */ + fn right_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { + match node { + Some(node) => { + let child = node.borrow().left.clone().unwrap(); + let grand_child = child.borrow().right.clone(); + // child を支点として node を右回転させる + child.borrow_mut().right = Some(node.clone()); + node.borrow_mut().left = grand_child; + // ノードの高さを更新する + Self::update_height(Some(node)); + Self::update_height(Some(child.clone())); + // 回転後の部分木の根ノードを返す + Some(child) + } + None => None, + } + } + + /* 左回転 */ + fn left_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { + match node { + Some(node) => { + let child = node.borrow().right.clone().unwrap(); + let grand_child = child.borrow().left.clone(); + // child を支点として node を左回転させる + child.borrow_mut().left = Some(node.clone()); + node.borrow_mut().right = grand_child; + // ノードの高さを更新する + Self::update_height(Some(node)); + Self::update_height(Some(child.clone())); + // 回転後の部分木の根ノードを返す + Some(child) + } + None => None, + } + } + + /* 回転操作を行い、この部分木の平衡を回復する */ + fn rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc { + // ノード node の平衡係数を取得 + let balance_factor = Self::balance_factor(node.clone()); + // 左に偏った木 + if balance_factor > 1 { + let node = node.unwrap(); + if Self::balance_factor(node.borrow().left.clone()) >= 0 { + // 右回転 + Self::right_rotate(Some(node)) + } else { + // 左回転してから右回転 + let left = node.borrow().left.clone(); + node.borrow_mut().left = Self::left_rotate(left); + Self::right_rotate(Some(node)) + } + } + // 右に偏った木 + else if balance_factor < -1 { + let node = node.unwrap(); + if Self::balance_factor(node.borrow().right.clone()) <= 0 { + // 左回転 + Self::left_rotate(Some(node)) + } else { + // 右回転してから左回転 + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::right_rotate(right); + Self::left_rotate(Some(node)) + } + } else { + // 平衡木なので回転不要、そのまま返す + node + } + } + + /* ノードを挿入 */ + fn insert(&mut self, val: i32) { + self.root = Self::insert_helper(self.root.clone(), val); + } + + /* ノードを再帰的に挿入する(補助メソッド) */ + fn insert_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { + match node { + Some(mut node) => { + /* 1. 挿入位置を探索してノードを挿入 */ + match { + let node_val = node.borrow().val; + node_val + } + .cmp(&val) + { + Ordering::Greater => { + let left = node.borrow().left.clone(); + node.borrow_mut().left = Self::insert_helper(left, val); + } + Ordering::Less => { + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::insert_helper(right, val); + } + Ordering::Equal => { + return Some(node); // 重複ノードは挿入せず、そのまま返す + } + } + Self::update_height(Some(node.clone())); // ノードの高さを更新する + + /* 2. 回転操作を行い、部分木の平衡を回復する */ + node = Self::rotate(Some(node)).unwrap(); + // 部分木の根ノードを返す + Some(node) + } + None => Some(TreeNode::new(val)), + } + } + + /* ノードを削除 */ + fn remove(&self, val: i32) { + Self::remove_helper(self.root.clone(), val); + } + + /* ノードを再帰的に削除する(補助メソッド) */ + fn remove_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc { + match node { + Some(mut node) => { + /* 1. ノードを探索して削除 */ + if val < node.borrow().val { + let left = node.borrow().left.clone(); + node.borrow_mut().left = Self::remove_helper(left, val); + } else if val > node.borrow().val { + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::remove_helper(right, val); + } else if node.borrow().left.is_none() || node.borrow().right.is_none() { + let child = if node.borrow().left.is_some() { + node.borrow().left.clone() + } else { + node.borrow().right.clone() + }; + match child { + // 子ノード数 = 0 の場合、node をそのまま削除して返す + None => { + return None; + } + // 子ノード数 = 1 の場合、node をそのまま削除する + Some(child) => node = child, + } + } else { + // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える + let mut temp = node.borrow().right.clone().unwrap(); + loop { + let temp_left = temp.borrow().left.clone(); + if temp_left.is_none() { + break; + } + temp = temp_left.unwrap(); + } + let right = node.borrow().right.clone(); + node.borrow_mut().right = Self::remove_helper(right, temp.borrow().val); + node.borrow_mut().val = temp.borrow().val; + } + Self::update_height(Some(node.clone())); // ノードの高さを更新する + + /* 2. 回転操作を行い、部分木の平衡を回復する */ + node = Self::rotate(Some(node)).unwrap(); + // 部分木の根ノードを返す + Some(node) + } + None => None, + } + } + + /* ノードを探索 */ + fn search(&self, val: i32) -> OptionTreeNodeRc { + let mut cur = self.root.clone(); + // ループで探索し、葉ノードを越えたら抜ける + while let Some(current) = cur.clone() { + match current.borrow().val.cmp(&val) { + // 目標ノードは cur の右部分木にある + Ordering::Less => { + cur = current.borrow().right.clone(); + } + // 目標ノードは cur の左部分木にある + Ordering::Greater => { + cur = current.borrow().left.clone(); + } + // 目標ノードが見つかったらループを抜ける + Ordering::Equal => { + break; + } + } + } + // 目標ノードを返す + cur + } +} + +/* Driver Code */ +fn main() { + fn test_insert(tree: &mut AVLTree, val: i32) { + tree.insert(val); + println!("\nノード {} を挿入した後、AVL 木は", val); + print_util::print_tree(&tree.root.clone().unwrap()); + } + + fn test_remove(tree: &mut AVLTree, val: i32) { + tree.remove(val); + println!("\nノード {} を削除した後、AVL 木は", val); + print_util::print_tree(&tree.root.clone().unwrap()); + } + + /* 空の AVL 木を初期化する */ + let mut avl_tree = AVLTree::new(); + + /* ノードを挿入 */ + // ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい + test_insert(&mut avl_tree, 1); + test_insert(&mut avl_tree, 2); + test_insert(&mut avl_tree, 3); + test_insert(&mut avl_tree, 4); + test_insert(&mut avl_tree, 5); + test_insert(&mut avl_tree, 8); + test_insert(&mut avl_tree, 7); + test_insert(&mut avl_tree, 9); + test_insert(&mut avl_tree, 10); + test_insert(&mut avl_tree, 6); + + /* 重複ノードを挿入する */ + test_insert(&mut avl_tree, 7); + + /* ノードを削除 */ + // ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい + test_remove(&mut avl_tree, 8); // 次数 0 のノードを削除する + test_remove(&mut avl_tree, 5); // 次数 1 のノードを削除する + test_remove(&mut avl_tree, 4); // 次数 2 のノードを削除する + + /* ノードを検索 */ + let node = avl_tree.search(7); + if let Some(node) = node { + println!( + "\n見つかったノードオブジェクトは {:?}、ノードの値 = {}", + &*node.borrow(), + node.borrow().val + ); + } +} diff --git a/ja/codes/rust/chapter_tree/binary_search_tree.rs b/ja/codes/rust/chapter_tree/binary_search_tree.rs new file mode 100644 index 000000000..54a777cb6 --- /dev/null +++ b/ja/codes/rust/chapter_tree/binary_search_tree.rs @@ -0,0 +1,195 @@ +/* + * File: binary_search_tree.rs + * Created Time: 2023-04-20 + * Author: xBLACKICEx (xBLACKICE@outlook.com)、night-cruise (2586447362@qq.com) + */ + +use hello_algo_rust::include::print_util; + +use std::cell::RefCell; +use std::cmp::Ordering; +use std::rc::Rc; + +use hello_algo_rust::include::TreeNode; + +type OptionTreeNodeRc = Option>>; + +/* 二分探索木 */ +pub struct BinarySearchTree { + root: OptionTreeNodeRc, +} + +impl BinarySearchTree { + /* コンストラクタ */ + pub fn new() -> Self { + // 空の木を初期化する + Self { root: None } + } + + /* 二分木の根ノードを取得 */ + pub fn get_root(&self) -> OptionTreeNodeRc { + self.root.clone() + } + + /* ノードを探索 */ + pub fn search(&self, num: i32) -> OptionTreeNodeRc { + let mut cur = self.root.clone(); + // ループで探索し、葉ノードを越えたら抜ける + while let Some(node) = cur.clone() { + match num.cmp(&node.borrow().val) { + // 目標ノードは cur の右部分木にある + Ordering::Greater => cur = node.borrow().right.clone(), + // 目標ノードは cur の左部分木にある + Ordering::Less => cur = node.borrow().left.clone(), + // 目標ノードが見つかったらループを抜ける + Ordering::Equal => break, + } + } + + // 目標ノードを返す + cur + } + + /* ノードを挿入 */ + pub fn insert(&mut self, num: i32) { + // 木が空なら、根ノードを初期化する + if self.root.is_none() { + self.root = Some(TreeNode::new(num)); + return; + } + let mut cur = self.root.clone(); + let mut pre = None; + // ループで探索し、葉ノードを越えたら抜ける + while let Some(node) = cur.clone() { + match num.cmp(&node.borrow().val) { + // 重複ノードが見つかったら、直ちに返す + Ordering::Equal => return, + // 挿入位置は cur の右部分木にある + Ordering::Greater => { + pre = cur.clone(); + cur = node.borrow().right.clone(); + } + // 挿入位置は cur の左部分木にある + Ordering::Less => { + pre = cur.clone(); + cur = node.borrow().left.clone(); + } + } + } + // ノードを挿入 + let pre = pre.unwrap(); + let node = Some(TreeNode::new(num)); + if num > pre.borrow().val { + pre.borrow_mut().right = node; + } else { + pre.borrow_mut().left = node; + } + } + + /* ノードを削除 */ + pub fn remove(&mut self, num: i32) { + // 木が空なら、そのまま早期リターンする + if self.root.is_none() { + return; + } + let mut cur = self.root.clone(); + let mut pre = None; + // ループで探索し、葉ノードを越えたら抜ける + while let Some(node) = cur.clone() { + match num.cmp(&node.borrow().val) { + // 削除対象のノードが見つかったら、ループを抜ける + Ordering::Equal => break, + // 削除対象ノードは cur の右部分木にある + Ordering::Greater => { + pre = cur.clone(); + cur = node.borrow().right.clone(); + } + // 削除対象ノードは cur の左部分木にある + Ordering::Less => { + pre = cur.clone(); + cur = node.borrow().left.clone(); + } + } + } + // 削除対象ノードがなければそのまま返す + if cur.is_none() { + return; + } + let cur = cur.unwrap(); + let (left_child, right_child) = (cur.borrow().left.clone(), cur.borrow().right.clone()); + match (left_child.clone(), right_child.clone()) { + // 子ノード数 = 0 or 1 + (None, None) | (Some(_), None) | (None, Some(_)) => { + // 子ノード数 = 0 / 1 のとき、child = nullptr / その子ノード + let child = left_child.or(right_child); + let pre = pre.unwrap(); + // ノード cur を削除する + if !Rc::ptr_eq(&cur, self.root.as_ref().unwrap()) { + let left = pre.borrow().left.clone(); + if left.is_some() && Rc::ptr_eq(left.as_ref().unwrap(), &cur) { + pre.borrow_mut().left = child; + } else { + pre.borrow_mut().right = child; + } + } else { + // 削除ノードが根ノードなら、根ノードを再設定 + self.root = child; + } + } + // 子ノード数 = 2 + (Some(_), Some(_)) => { + // 中順走査における cur の次ノードを取得 + let mut tmp = cur.borrow().right.clone(); + while let Some(node) = tmp.clone() { + if node.borrow().left.is_some() { + tmp = node.borrow().left.clone(); + } else { + break; + } + } + let tmp_val = tmp.unwrap().borrow().val; + // ノード tmp を再帰的に削除 + self.remove(tmp_val); + // tmp で cur を上書きする + cur.borrow_mut().val = tmp_val; + } + } + } +} + +/* Driver Code */ +fn main() { + /* 二分探索木を初期化 */ + let mut bst = BinarySearchTree::new(); + // 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる + let nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; + for &num in &nums { + bst.insert(num); + } + println!("\n初期化した二分木は\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); + + /* ノードを検索 */ + let node = bst.search(7); + println!( + "\n見つかったノードオブジェクトは {:?}、ノードの値 = {}", + node.clone().unwrap(), + node.clone().unwrap().borrow().val + ); + + /* ノードを挿入 */ + bst.insert(16); + println!("\nノード 16 を挿入した後、二分木は\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); + + /* ノードを削除 */ + bst.remove(1); + println!("\nノード 1 を削除した後、二分木は\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); + bst.remove(2); + println!("\nノード 2 を削除した後、二分木は\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); + bst.remove(4); + println!("\nノード 4 を削除した後、二分木は\n"); + print_util::print_tree(bst.get_root().as_ref().unwrap()); +} diff --git a/ja/codes/rust/chapter_tree/binary_tree.rs b/ja/codes/rust/chapter_tree/binary_tree.rs new file mode 100644 index 000000000..cd9251a56 --- /dev/null +++ b/ja/codes/rust/chapter_tree/binary_tree.rs @@ -0,0 +1,38 @@ +/** + * File: binary_tree.rs + * Created Time: 2023-02-27 + * Author: xBLACKICEx (xBLACKICE@outlook.com) + */ +use std::rc::Rc; +use hello_algo_rust::include::{print_util, TreeNode}; + +/* Driver Code */ +fn main() { + /* 二分木を初期化 */ + // ノードを初期化 + let n1 = TreeNode::new(1); + let n2 = TreeNode::new(2); + let n3 = TreeNode::new(3); + let n4 = TreeNode::new(4); + let n5 = TreeNode::new(5); + // ノード間の参照(ポインタ)を構築する + n1.borrow_mut().left = Some(Rc::clone(&n2)); + n1.borrow_mut().right = Some(Rc::clone(&n3)); + n2.borrow_mut().left = Some(Rc::clone(&n4)); + n2.borrow_mut().right = Some(Rc::clone(&n5)); + println!("\n二分木を初期化\n"); + print_util::print_tree(&n1); + + // ノードの挿入と削除 + let p = TreeNode::new(0); + // n1 -> n2 の間にノード P を挿入 + p.borrow_mut().left = Some(Rc::clone(&n2)); + n1.borrow_mut().left = Some(Rc::clone(&p)); + println!("\nノード P を挿入した後\n"); + print_util::print_tree(&n1); + // ノード P を削除 + drop(p); + n1.borrow_mut().left = Some(Rc::clone(&n2)); + println!("\nノード P を削除した後\n"); + print_util::print_tree(&n1); +} diff --git a/ja/codes/rust/chapter_tree/binary_tree_bfs.rs b/ja/codes/rust/chapter_tree/binary_tree_bfs.rs new file mode 100644 index 000000000..bc1882d72 --- /dev/null +++ b/ja/codes/rust/chapter_tree/binary_tree_bfs.rs @@ -0,0 +1,45 @@ +/* + * File: binary_tree_bfs.rs + * Created Time: 2023-04-07 + * Author: xBLACKICEx (xBLACKICE@outlook.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use hello_algo_rust::op_vec; + +use std::collections::VecDeque; +use std::{cell::RefCell, rc::Rc}; + +/* レベル順走査 */ +fn level_order(root: &Rc>) -> Vec { + // キューを初期化し、ルートノードを追加する + let mut que = VecDeque::new(); + que.push_back(root.clone()); + // 走査順序を保存するためのリストを初期化する + let mut vec = Vec::new(); + + while let Some(node) = que.pop_front() { + // デキュー + vec.push(node.borrow().val); // ノードの値を保存する + if let Some(left) = node.borrow().left.as_ref() { + que.push_back(left.clone()); // 左子ノードをキューに追加 + } + if let Some(right) = node.borrow().right.as_ref() { + que.push_back(right.clone()); // 右子ノードをキューに追加 + }; + } + vec +} + +/* Driver Code */ +fn main() { + /* 二分木を初期化 */ + // ここでは、配列から直接二分木を生成する関数を利用する + let root = vec_to_tree(op_vec![1, 2, 3, 4, 5, 6, 7]).unwrap(); + println!("二分木を初期化\n"); + print_util::print_tree(&root); + + /* レベル順走査 */ + let vec = level_order(&root); + print!("\nレベル順走査のノード出力シーケンス = {:?}", vec); +} diff --git a/ja/codes/rust/chapter_tree/binary_tree_dfs.rs b/ja/codes/rust/chapter_tree/binary_tree_dfs.rs new file mode 100644 index 000000000..741c22efe --- /dev/null +++ b/ja/codes/rust/chapter_tree/binary_tree_dfs.rs @@ -0,0 +1,87 @@ +/* + * File: binary_tree_dfs.rs + * Created Time: 2023-04-06 + * Author: xBLACKICEx (xBLACKICE@outlook.com) + */ + +use hello_algo_rust::include::{print_util, vec_to_tree, TreeNode}; +use hello_algo_rust::op_vec; + +use std::cell::RefCell; +use std::rc::Rc; + +/* 先行順走査 */ +fn pre_order(root: Option<&Rc>>) -> Vec { + let mut result = vec![]; + + fn dfs(root: Option<&Rc>>, res: &mut Vec) { + if let Some(node) = root { + // 訪問順序:根ノード -> 左部分木 -> 右部分木 + let node = node.borrow(); + res.push(node.val); + dfs(node.left.as_ref(), res); + dfs(node.right.as_ref(), res); + } + } + dfs(root, &mut result); + + result +} + +/* 中順走査 */ +fn in_order(root: Option<&Rc>>) -> Vec { + let mut result = vec![]; + + fn dfs(root: Option<&Rc>>, res: &mut Vec) { + if let Some(node) = root { + // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 + let node = node.borrow(); + dfs(node.left.as_ref(), res); + res.push(node.val); + dfs(node.right.as_ref(), res); + } + } + dfs(root, &mut result); + + result +} + +/* 後順走査 */ +fn post_order(root: Option<&Rc>>) -> Vec { + let mut result = vec![]; + + fn dfs(root: Option<&Rc>>, res: &mut Vec) { + if let Some(node) = root { + // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード + let node = node.borrow(); + dfs(node.left.as_ref(), res); + dfs(node.right.as_ref(), res); + res.push(node.val); + } + } + + dfs(root, &mut result); + + result +} + +/* Driver Code */ +fn main() { + /* 二分木を初期化 */ + // ここでは、配列から直接二分木を生成する関数を利用する + let root = vec_to_tree(op_vec![1, 2, 3, 4, 5, 6, 7]); + println!("二分木を初期化\n"); + print_util::print_tree(root.as_ref().unwrap()); + + /* 先行順走査 */ + let vec = pre_order(root.as_ref()); + println!("\n前順走査のノード出力シーケンス = {:?}", vec); + + /* 中順走査 */ + let vec = in_order(root.as_ref()); + println!("\n中順走査のノード出力シーケンス = {:?}", vec); + + /* 後順走査 */ + let vec = post_order(root.as_ref()); + print!("\n後順走査のノード出力シーケンス = {:?}", vec); +} diff --git a/ja/codes/rust/src/include/list_node.rs b/ja/codes/rust/src/include/list_node.rs new file mode 100644 index 000000000..4dd114699 --- /dev/null +++ b/ja/codes/rust/src/include/list_node.rs @@ -0,0 +1,57 @@ +/* + * File: list_node.rs + * Created Time: 2023-03-05 + * Author: codingonion (coderonion@gmail.com), rongyi (hiarongyi@gmail.com) + */ + +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +#[derive(Debug)] +pub struct ListNode { + pub val: T, + pub next: Option>>>, +} + +impl ListNode { + pub fn new(val: T) -> Rc>> { + Rc::new(RefCell::new(ListNode { val, next: None })) + } + + /* 配列をデシリアライズして連結リストに変換する */ + pub fn arr_to_linked_list(array: &[T]) -> Option>>> + where + T: Copy + Clone, + { + let mut head = None; + // insert in reverse order + for item in array.iter().rev() { + let node = Rc::new(RefCell::new(ListNode { + val: *item, + next: head.take(), + })); + head = Some(node); + } + head + } + + /* 連結リストをハッシュテーブルに変換 */ + pub fn linked_list_to_hashmap( + linked_list: Option>>>, + ) -> HashMap>>> + where + T: std::hash::Hash + Eq + Copy + Clone, + { + let mut hashmap = HashMap::new(); + let mut node = linked_list; + + while let Some(cur) = node { + let borrow = cur.borrow(); + hashmap.insert(borrow.val.clone(), cur.clone()); + node = borrow.next.clone(); + } + + hashmap + } +} diff --git a/ja/codes/rust/src/include/mod.rs b/ja/codes/rust/src/include/mod.rs new file mode 100644 index 000000000..6cba6f9a5 --- /dev/null +++ b/ja/codes/rust/src/include/mod.rs @@ -0,0 +1,16 @@ +/* + * File: include.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICE@outlook.com) + */ + +pub mod list_node; +pub mod print_util; +pub mod tree_node; +pub mod vertex; + +// rexport to include +pub use list_node::*; +pub use print_util::*; +pub use tree_node::*; +pub use vertex::*; diff --git a/ja/codes/rust/src/include/print_util.rs b/ja/codes/rust/src/include/print_util.rs new file mode 100644 index 000000000..0f30aaf39 --- /dev/null +++ b/ja/codes/rust/src/include/print_util.rs @@ -0,0 +1,103 @@ +/* + * File: print_util.rs + * Created Time: 2023-02-05 + * Author: codingonion (coderonion@gmail.com), xBLACKICEx (xBLACKICEx@outlook.com) + */ + +use std::cell::{Cell, RefCell}; +use std::fmt::Display; +use std::collections::{HashMap, VecDeque}; +use std::rc::Rc; + +use super::list_node::ListNode; +use super::tree_node::{TreeNode, vec_to_tree}; + +struct Trunk<'a, 'b> { + prev: Option<&'a Trunk<'a, 'b>>, + str: Cell<&'b str>, +} + +/* 配列を出力する */ +pub fn print_array(nums: &[T]) { + print!("["); + if nums.len() > 0 { + for (i, num) in nums.iter().enumerate() { + print!("{}{}", num, if i == nums.len() - 1 {"]"} else {", "} ); + } + } else { + print!("]"); + } +} + +/* ハッシュテーブルを出力 */ +pub fn print_hash_map(map: &HashMap) { + for (key, value) in map { + println!("{key} -> {value}"); + } +} + +/* キューを出力(双方向キュー) */ +pub fn print_queue(queue: &VecDeque) { + print!("["); + let iter = queue.iter(); + for (i, data) in iter.enumerate() { + print!("{}{}", data, if i == queue.len() - 1 {"]"} else {", "} ); + } +} + +/* 連結リストを出力 */ +pub fn print_linked_list(head: &Rc>>) { + print!("{}{}", head.borrow().val, if head.borrow().next.is_none() {"\n"} else {" -> "}); + if let Some(node) = &head.borrow().next { + return print_linked_list(node); + } +} + +/* 二分木を出力 */ +pub fn print_tree(root: &Rc>) { + _print_tree(Some(root), None, false); +} + +/* 二分木を出力 */ +fn _print_tree(root: Option<&Rc>>, prev: Option<&Trunk>, is_right: bool) { + if let Some(node) = root { + let mut prev_str = " "; + let trunk = Trunk { prev, str: Cell::new(prev_str) }; + _print_tree(node.borrow().right.as_ref(), Some(&trunk), true); + + if prev.is_none() { + trunk.str.set("———"); + } else if is_right { + trunk.str.set("/———"); + prev_str = " |"; + } else { + trunk.str.set("\\———"); + prev.as_ref().unwrap().str.set(prev_str); + } + + show_trunks(Some(&trunk)); + println!(" {}", node.borrow().val); + if let Some(prev) = prev { + prev.str.set(prev_str); + } + trunk.str.set(" |"); + + _print_tree(node.borrow().left.as_ref(), Some(&trunk), false); + } +} + +fn show_trunks(trunk: Option<&Trunk>) { + if let Some(trunk) = trunk { + show_trunks(trunk.prev); + print!("{}", trunk.str.get()); + } +} + +/* ヒープを出力 */ +pub fn print_heap(heap: Vec) { + println!("ヒープの配列表現:{:?}", heap); + println!("ヒープの木構造表現:"); + if let Some(root) = vec_to_tree(heap.into_iter().map(|val| Some(val)).collect()) { + print_tree(&root); + } +} diff --git a/ja/codes/rust/src/include/tree_node.rs b/ja/codes/rust/src/include/tree_node.rs new file mode 100644 index 000000000..f8854ba54 --- /dev/null +++ b/ja/codes/rust/src/include/tree_node.rs @@ -0,0 +1,92 @@ +/* + * File: tree_node.rs + * Created Time: 2023-02-27 + * Author: xBLACKICEx (xBLACKICE@outlook.com), night-cruise (2586447362@qq.com) + */ + +use std::cell::RefCell; +use std::rc::Rc; + +/* 二分木ノード型 */ +#[derive(Debug)] +pub struct TreeNode { + pub val: i32, + pub height: i32, + pub parent: Option>>, + pub left: Option>>, + pub right: Option>>, +} + +impl TreeNode { + /* コンストラクタ */ + pub fn new(val: i32) -> Rc> { + Rc::new(RefCell::new(Self { + val, + height: 0, + parent: None, + left: None, + right: None, + })) + } +} + +#[macro_export] +macro_rules! op_vec { + ( $( $x:expr ),* ) => { + vec![ + $(Option::from($x)),* + ] + }; +} + +// シリアライズの符号化規則は以下を参照: +// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ +// 二分木の配列表現: +// [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] +// 二分木の連結リスト表現: +// /——— 15 +// /——— 7 +// /——— 3 +// | \——— 6 +// | \——— 12 +// ——— 1 +// \——— 2 +// | /——— 9 +// \——— 4 +// \——— 8 + +/* リストを二分木にデシリアライズする: 再帰 */ +fn vec_to_tree_dfs(arr: &[Option], i: usize) -> Option>> { + if i >= arr.len() || arr[i].is_none() { + return None; + } + let root = TreeNode::new(arr[i].unwrap()); + root.borrow_mut().left = vec_to_tree_dfs(arr, 2 * i + 1); + root.borrow_mut().right = vec_to_tree_dfs(arr, 2 * i + 2); + Some(root) +} + +/* リストを二分木にデシリアライズする */ +pub fn vec_to_tree(arr: Vec>) -> Option>> { + vec_to_tree_dfs(&arr, 0) +} + +/* 二分木をリストにシリアライズする: 再帰 */ +fn tree_to_vec_dfs(root: Option<&Rc>>, i: usize, res: &mut Vec>) { + if let Some(root) = root { + // i + 1 is the minimum valid size to access index i + while res.len() < i + 1 { + res.push(None); + } + res[i] = Some(root.borrow().val); + tree_to_vec_dfs(root.borrow().left.as_ref(), 2 * i + 1, res); + tree_to_vec_dfs(root.borrow().right.as_ref(), 2 * i + 2, res); + } +} + +/* 二分木をリストにシリアライズする */ +pub fn tree_to_vec(root: Option>>) -> Vec> { + let mut res = vec![]; + tree_to_vec_dfs(root.as_ref(), 0, &mut res); + res +} diff --git a/ja/codes/rust/src/include/vertex.rs b/ja/codes/rust/src/include/vertex.rs new file mode 100644 index 000000000..f2c72058d --- /dev/null +++ b/ja/codes/rust/src/include/vertex.rs @@ -0,0 +1,27 @@ +/* + * File: vertex.rs + * Created Time: 2023-07-13 + * Author: night-cruise (2586447362@qq.com) + */ + +/* 頂点の型 */ +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct Vertex { + pub val: i32, +} + +impl From for Vertex { + fn from(value: i32) -> Self { + Self { val: value } + } +} + +/* 値リスト vals を入力し、頂点リスト vets を返す */ +pub fn vals_to_vets(vals: Vec) -> Vec { + vals.into_iter().map(|val| val.into()).collect() +} + +/* 頂点リスト vets を入力し、値リスト vals を返す */ +pub fn vets_to_vals(vets: Vec) -> Vec { + vets.into_iter().map(|vet| vet.val).collect() +} diff --git a/ja/codes/rust/src/lib.rs b/ja/codes/rust/src/lib.rs new file mode 100644 index 000000000..2883b9104 --- /dev/null +++ b/ja/codes/rust/src/lib.rs @@ -0,0 +1 @@ +pub mod include; diff --git a/ja/codes/swift/.gitignore b/ja/codes/swift/.gitignore new file mode 100644 index 000000000..6295af4cc --- /dev/null +++ b/ja/codes/swift/.gitignore @@ -0,0 +1,130 @@ +# Created by https://www.toptal.com/developers/gitignore/api/objective-c,swift,swiftpackagemanager +# Edit at https://www.toptal.com/developers/gitignore?templates=objective-c,swift,swiftpackagemanager + +### Objective-C ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# fastlane +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +### Objective-C Patch ### + +### Swift ### +# Xcode +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + + + + + + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + + +### SwiftPackageManager ### +Packages +xcuserdata +*.xcodeproj + + +# End of https://www.toptal.com/developers/gitignore/api/objective-c,swift,swiftpackagemanager diff --git a/ja/codes/swift/Package.resolved b/ja/codes/swift/Package.resolved new file mode 100644 index 000000000..159c83d26 --- /dev/null +++ b/ja/codes/swift/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "branch" : "release/1.1", + "revision" : "4a1d92ba85027010d2c528c05576cde9a362254b" + } + } + ], + "version" : 2 +} diff --git a/ja/codes/swift/Package.swift b/ja/codes/swift/Package.swift new file mode 100644 index 000000000..5326dc138 --- /dev/null +++ b/ja/codes/swift/Package.swift @@ -0,0 +1,206 @@ +// swift-tools-version: 5.7 + +import PackageDescription + +let package = Package( + name: "HelloAlgo", + products: [ + // chapter_computational_complexity + .executable(name: "iteration", targets: ["iteration"]), + .executable(name: "recursion", targets: ["recursion"]), + .executable(name: "time_complexity", targets: ["time_complexity"]), + .executable(name: "worst_best_time_complexity", targets: ["worst_best_time_complexity"]), + .executable(name: "space_complexity", targets: ["space_complexity"]), + // chapter_array_and_linkedlist + .executable(name: "array", targets: ["array"]), + .executable(name: "linked_list", targets: ["linked_list"]), + .executable(name: "list", targets: ["list"]), + .executable(name: "my_list", targets: ["my_list"]), + // chapter_stack_and_queue + .executable(name: "stack", targets: ["stack"]), + .executable(name: "linkedlist_stack", targets: ["linkedlist_stack"]), + .executable(name: "array_stack", targets: ["array_stack"]), + .executable(name: "queue", targets: ["queue"]), + .executable(name: "linkedlist_queue", targets: ["linkedlist_queue"]), + .executable(name: "array_queue", targets: ["array_queue"]), + .executable(name: "deque", targets: ["deque"]), + .executable(name: "linkedlist_deque", targets: ["linkedlist_deque"]), + .executable(name: "array_deque", targets: ["array_deque"]), + // chapter_hashing + .executable(name: "hash_map", targets: ["hash_map"]), + .executable(name: "array_hash_map", targets: ["array_hash_map"]), + .executable(name: "hash_map_chaining", targets: ["hash_map_chaining"]), + .executable(name: "hash_map_open_addressing", targets: ["hash_map_open_addressing"]), + .executable(name: "simple_hash", targets: ["simple_hash"]), + .executable(name: "built_in_hash", targets: ["built_in_hash"]), + // chapter_tree + .executable(name: "binary_tree", targets: ["binary_tree"]), + .executable(name: "binary_tree_bfs", targets: ["binary_tree_bfs"]), + .executable(name: "binary_tree_dfs", targets: ["binary_tree_dfs"]), + .executable(name: "array_binary_tree", targets: ["array_binary_tree"]), + .executable(name: "binary_search_tree", targets: ["binary_search_tree"]), + .executable(name: "avl_tree", targets: ["avl_tree"]), + // chapter_heap + .executable(name: "heap", targets: ["heap"]), + .executable(name: "my_heap", targets: ["my_heap"]), + .executable(name: "top_k", targets: ["top_k"]), + // chapter_graph + .executable(name: "graph_adjacency_matrix", targets: ["graph_adjacency_matrix"]), + .executable(name: "graph_adjacency_list", targets: ["graph_adjacency_list"]), + .executable(name: "graph_bfs", targets: ["graph_bfs"]), + .executable(name: "graph_dfs", targets: ["graph_dfs"]), + // chapter_searching + .executable(name: "binary_search", targets: ["binary_search"]), + .executable(name: "binary_search_insertion", targets: ["binary_search_insertion"]), + .executable(name: "binary_search_edge", targets: ["binary_search_edge"]), + .executable(name: "two_sum", targets: ["two_sum"]), + .executable(name: "linear_search", targets: ["linear_search"]), + .executable(name: "hashing_search", targets: ["hashing_search"]), + // chapter_sorting + .executable(name: "selection_sort", targets: ["selection_sort"]), + .executable(name: "bubble_sort", targets: ["bubble_sort"]), + .executable(name: "insertion_sort", targets: ["insertion_sort"]), + .executable(name: "quick_sort", targets: ["quick_sort"]), + .executable(name: "merge_sort", targets: ["merge_sort"]), + .executable(name: "heap_sort", targets: ["heap_sort"]), + .executable(name: "bucket_sort", targets: ["bucket_sort"]), + .executable(name: "counting_sort", targets: ["counting_sort"]), + .executable(name: "radix_sort", targets: ["radix_sort"]), + // chapter_divide_and_conquer + .executable(name: "binary_search_recur", targets: ["binary_search_recur"]), + .executable(name: "build_tree", targets: ["build_tree"]), + .executable(name: "hanota", targets: ["hanota"]), + // chapter_backtracking + .executable(name: "preorder_traversal_i_compact", targets: ["preorder_traversal_i_compact"]), + .executable(name: "preorder_traversal_ii_compact", targets: ["preorder_traversal_ii_compact"]), + .executable(name: "preorder_traversal_iii_compact", targets: ["preorder_traversal_iii_compact"]), + .executable(name: "preorder_traversal_iii_template", targets: ["preorder_traversal_iii_template"]), + .executable(name: "permutations_i", targets: ["permutations_i"]), + .executable(name: "permutations_ii", targets: ["permutations_ii"]), + .executable(name: "subset_sum_i_naive", targets: ["subset_sum_i_naive"]), + .executable(name: "subset_sum_i", targets: ["subset_sum_i"]), + .executable(name: "subset_sum_ii", targets: ["subset_sum_ii"]), + .executable(name: "n_queens", targets: ["n_queens"]), + // chapter_dynamic_programming + .executable(name: "climbing_stairs_backtrack", targets: ["climbing_stairs_backtrack"]), + .executable(name: "climbing_stairs_dfs", targets: ["climbing_stairs_dfs"]), + .executable(name: "climbing_stairs_dfs_mem", targets: ["climbing_stairs_dfs_mem"]), + .executable(name: "climbing_stairs_dp", targets: ["climbing_stairs_dp"]), + .executable(name: "min_cost_climbing_stairs_dp", targets: ["min_cost_climbing_stairs_dp"]), + .executable(name: "climbing_stairs_constraint_dp", targets: ["climbing_stairs_constraint_dp"]), + .executable(name: "min_path_sum", targets: ["min_path_sum"]), + .executable(name: "knapsack", targets: ["knapsack"]), + .executable(name: "unbounded_knapsack", targets: ["unbounded_knapsack"]), + .executable(name: "coin_change", targets: ["coin_change"]), + .executable(name: "coin_change_ii", targets: ["coin_change_ii"]), + .executable(name: "edit_distance", targets: ["edit_distance"]), + // chapter_greedy + .executable(name: "coin_change_greedy", targets: ["coin_change_greedy"]), + .executable(name: "fractional_knapsack", targets: ["fractional_knapsack"]), + .executable(name: "max_capacity", targets: ["max_capacity"]), + .executable(name: "max_product_cutting", targets: ["max_product_cutting"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-collections", branch: "release/1.1"), + ], + targets: [ + // helper + .target(name: "utils", path: "utils"), + .target(name: "graph_adjacency_list_target", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_list_target.swift"], swiftSettings: [.define("TARGET")]), + .target(name: "binary_search_insertion_target", path: "chapter_searching", sources: ["binary_search_insertion_target.swift"], swiftSettings: [.define("TARGET")]), + // chapter_computational_complexity + .executableTarget(name: "iteration", path: "chapter_computational_complexity", sources: ["iteration.swift"]), + .executableTarget(name: "recursion", path: "chapter_computational_complexity", sources: ["recursion.swift"]), + .executableTarget(name: "time_complexity", path: "chapter_computational_complexity", sources: ["time_complexity.swift"]), + .executableTarget(name: "worst_best_time_complexity", path: "chapter_computational_complexity", sources: ["worst_best_time_complexity.swift"]), + .executableTarget(name: "space_complexity", dependencies: ["utils"], path: "chapter_computational_complexity", sources: ["space_complexity.swift"]), + // chapter_array_and_linkedlist + .executableTarget(name: "array", path: "chapter_array_and_linkedlist", sources: ["array.swift"]), + .executableTarget(name: "linked_list", dependencies: ["utils"], path: "chapter_array_and_linkedlist", sources: ["linked_list.swift"]), + .executableTarget(name: "list", path: "chapter_array_and_linkedlist", sources: ["list.swift"]), + .executableTarget(name: "my_list", path: "chapter_array_and_linkedlist", sources: ["my_list.swift"]), + // chapter_stack_and_queue + .executableTarget(name: "stack", path: "chapter_stack_and_queue", sources: ["stack.swift"]), + .executableTarget(name: "linkedlist_stack", dependencies: ["utils"], path: "chapter_stack_and_queue", sources: ["linkedlist_stack.swift"]), + .executableTarget(name: "array_stack", path: "chapter_stack_and_queue", sources: ["array_stack.swift"]), + .executableTarget(name: "queue", path: "chapter_stack_and_queue", sources: ["queue.swift"]), + .executableTarget(name: "linkedlist_queue", dependencies: ["utils"], path: "chapter_stack_and_queue", sources: ["linkedlist_queue.swift"]), + .executableTarget(name: "array_queue", path: "chapter_stack_and_queue", sources: ["array_queue.swift"]), + .executableTarget(name: "deque", path: "chapter_stack_and_queue", sources: ["deque.swift"]), + .executableTarget(name: "linkedlist_deque", path: "chapter_stack_and_queue", sources: ["linkedlist_deque.swift"]), + .executableTarget(name: "array_deque", path: "chapter_stack_and_queue", sources: ["array_deque.swift"]), + // chapter_hashing + .executableTarget(name: "hash_map", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map.swift"]), + .executableTarget(name: "array_hash_map", dependencies: ["utils"], path: "chapter_hashing", sources: ["array_hash_map.swift"]), + .executableTarget(name: "hash_map_chaining", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map_chaining.swift"]), + .executableTarget(name: "hash_map_open_addressing", dependencies: ["utils"], path: "chapter_hashing", sources: ["hash_map_open_addressing.swift"]), + .executableTarget(name: "simple_hash", path: "chapter_hashing", sources: ["simple_hash.swift"]), + .executableTarget(name: "built_in_hash", dependencies: ["utils"], path: "chapter_hashing", sources: ["built_in_hash.swift"]), + // chapter_tree + .executableTarget(name: "binary_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree.swift"]), + .executableTarget(name: "binary_tree_bfs", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree_bfs.swift"]), + .executableTarget(name: "binary_tree_dfs", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_tree_dfs.swift"]), + .executableTarget(name: "array_binary_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["array_binary_tree.swift"]), + .executableTarget(name: "binary_search_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["binary_search_tree.swift"]), + .executableTarget(name: "avl_tree", dependencies: ["utils"], path: "chapter_tree", sources: ["avl_tree.swift"]), + // chapter_heap + .executableTarget(name: "heap", dependencies: ["utils", .product(name: "HeapModule", package: "swift-collections")], path: "chapter_heap", sources: ["heap.swift"]), + .executableTarget(name: "my_heap", dependencies: ["utils"], path: "chapter_heap", sources: ["my_heap.swift"]), + .executableTarget(name: "top_k", dependencies: ["utils", .product(name: "HeapModule", package: "swift-collections")], path: "chapter_heap", sources: ["top_k.swift"]), + // chapter_graph + .executableTarget(name: "graph_adjacency_matrix", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_matrix.swift"]), + .executableTarget(name: "graph_adjacency_list", dependencies: ["utils"], path: "chapter_graph", sources: ["graph_adjacency_list.swift"]), + .executableTarget(name: "graph_bfs", dependencies: ["utils", "graph_adjacency_list_target"], path: "chapter_graph", sources: ["graph_bfs.swift"]), + .executableTarget(name: "graph_dfs", dependencies: ["utils", "graph_adjacency_list_target"], path: "chapter_graph", sources: ["graph_dfs.swift"]), + // chapter_searching + .executableTarget(name: "binary_search", path: "chapter_searching", sources: ["binary_search.swift"]), + .executableTarget(name: "binary_search_insertion", path: "chapter_searching", sources: ["binary_search_insertion.swift"]), + .executableTarget(name: "binary_search_edge", dependencies: ["binary_search_insertion_target"], path: "chapter_searching", sources: ["binary_search_edge.swift"]), + .executableTarget(name: "two_sum", path: "chapter_searching", sources: ["two_sum.swift"]), + .executableTarget(name: "linear_search", dependencies: ["utils"], path: "chapter_searching", sources: ["linear_search.swift"]), + .executableTarget(name: "hashing_search", dependencies: ["utils"], path: "chapter_searching", sources: ["hashing_search.swift"]), + // chapter_sorting + .executableTarget(name: "selection_sort", path: "chapter_sorting", sources: ["selection_sort.swift"]), + .executableTarget(name: "bubble_sort", path: "chapter_sorting", sources: ["bubble_sort.swift"]), + .executableTarget(name: "insertion_sort", path: "chapter_sorting", sources: ["insertion_sort.swift"]), + .executableTarget(name: "quick_sort", path: "chapter_sorting", sources: ["quick_sort.swift"]), + .executableTarget(name: "merge_sort", path: "chapter_sorting", sources: ["merge_sort.swift"]), + .executableTarget(name: "heap_sort", path: "chapter_sorting", sources: ["heap_sort.swift"]), + .executableTarget(name: "bucket_sort", path: "chapter_sorting", sources: ["bucket_sort.swift"]), + .executableTarget(name: "counting_sort", path: "chapter_sorting", sources: ["counting_sort.swift"]), + .executableTarget(name: "radix_sort", path: "chapter_sorting", sources: ["radix_sort.swift"]), + // chapter_divide_and_conquer + .executableTarget(name: "binary_search_recur", path: "chapter_divide_and_conquer", sources: ["binary_search_recur.swift"]), + .executableTarget(name: "build_tree", dependencies: ["utils"], path: "chapter_divide_and_conquer", sources: ["build_tree.swift"]), + .executableTarget(name: "hanota", path: "chapter_divide_and_conquer", sources: ["hanota.swift"]), + // chapter_backtracking + .executableTarget(name: "preorder_traversal_i_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_i_compact.swift"]), + .executableTarget(name: "preorder_traversal_ii_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_ii_compact.swift"]), + .executableTarget(name: "preorder_traversal_iii_compact", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_iii_compact.swift"]), + .executableTarget(name: "preorder_traversal_iii_template", dependencies: ["utils"], path: "chapter_backtracking", sources: ["preorder_traversal_iii_template.swift"]), + .executableTarget(name: "permutations_i", path: "chapter_backtracking", sources: ["permutations_i.swift"]), + .executableTarget(name: "permutations_ii", path: "chapter_backtracking", sources: ["permutations_ii.swift"]), + .executableTarget(name: "subset_sum_i_naive", path: "chapter_backtracking", sources: ["subset_sum_i_naive.swift"]), + .executableTarget(name: "subset_sum_i", path: "chapter_backtracking", sources: ["subset_sum_i.swift"]), + .executableTarget(name: "subset_sum_ii", path: "chapter_backtracking", sources: ["subset_sum_ii.swift"]), + .executableTarget(name: "n_queens", path: "chapter_backtracking", sources: ["n_queens.swift"]), + // chapter_dynamic_programming + .executableTarget(name: "climbing_stairs_backtrack", path: "chapter_dynamic_programming", sources: ["climbing_stairs_backtrack.swift"]), + .executableTarget(name: "climbing_stairs_dfs", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs.swift"]), + .executableTarget(name: "climbing_stairs_dfs_mem", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dfs_mem.swift"]), + .executableTarget(name: "climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_dp.swift"]), + .executableTarget(name: "min_cost_climbing_stairs_dp", path: "chapter_dynamic_programming", sources: ["min_cost_climbing_stairs_dp.swift"]), + .executableTarget(name: "climbing_stairs_constraint_dp", path: "chapter_dynamic_programming", sources: ["climbing_stairs_constraint_dp.swift"]), + .executableTarget(name: "min_path_sum", path: "chapter_dynamic_programming", sources: ["min_path_sum.swift"]), + .executableTarget(name: "knapsack", path: "chapter_dynamic_programming", sources: ["knapsack.swift"]), + .executableTarget(name: "unbounded_knapsack", path: "chapter_dynamic_programming", sources: ["unbounded_knapsack.swift"]), + .executableTarget(name: "coin_change", path: "chapter_dynamic_programming", sources: ["coin_change.swift"]), + .executableTarget(name: "coin_change_ii", path: "chapter_dynamic_programming", sources: ["coin_change_ii.swift"]), + .executableTarget(name: "edit_distance", path: "chapter_dynamic_programming", sources: ["edit_distance.swift"]), + // chapter_greedy + .executableTarget(name: "coin_change_greedy", path: "chapter_greedy", sources: ["coin_change_greedy.swift"]), + .executableTarget(name: "fractional_knapsack", path: "chapter_greedy", sources: ["fractional_knapsack.swift"]), + .executableTarget(name: "max_capacity", path: "chapter_greedy", sources: ["max_capacity.swift"]), + .executableTarget(name: "max_product_cutting", path: "chapter_greedy", sources: ["max_product_cutting.swift"]), + ] +) diff --git a/ja/codes/swift/chapter_array_and_linkedlist/array.swift b/ja/codes/swift/chapter_array_and_linkedlist/array.swift new file mode 100644 index 000000000..8f9c8c209 --- /dev/null +++ b/ja/codes/swift/chapter_array_and_linkedlist/array.swift @@ -0,0 +1,107 @@ +/** + * File: array.swift + * Created Time: 2023-01-05 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 要素へランダムアクセス */ +func randomAccess(nums: [Int]) -> Int { + // 区間 [0, nums.count) からランダムに数字を 1 つ選ぶ + let randomIndex = nums.indices.randomElement()! + // ランダムな要素を取得して返す + let randomNum = nums[randomIndex] + return randomNum +} + +/* 配列長を拡張する */ +func extend(nums: [Int], enlarge: Int) -> [Int] { + // 拡張後の長さを持つ配列を初期化する + var res = Array(repeating: 0, count: nums.count + enlarge) + // 元の配列の全要素を新しい配列にコピー + for i in nums.indices { + res[i] = nums[i] + } + // 拡張後の新しい配列を返す + return res +} + +/* 配列の index 番目に要素 num を挿入 */ +func insert(nums: inout [Int], num: Int, index: Int) { + // インデックス index 以降の全要素を 1 つ後ろへ移動する + for i in nums.indices.dropFirst(index).reversed() { + nums[i] = nums[i - 1] + } + // index の要素に num を代入する + nums[index] = num +} + +/* index の要素を削除する */ +func remove(nums: inout [Int], index: Int) { + // インデックス index より後ろの全要素を 1 つ前へ移動する + for i in nums.indices.dropFirst(index).dropLast() { + nums[i] = nums[i + 1] + } +} + +/* 配列を走査 */ +func traverse(nums: [Int]) { + var count = 0 + // インデックスで配列を走査 + for i in nums.indices { + count += nums[i] + } + // 配列要素を直接走査 + for num in nums { + count += num + } + // データのインデックスと要素を同時に走査する + for (i, num) in nums.enumerated() { + count += nums[i] + count += num + } +} + +/* 配列内で指定要素を探す */ +func find(nums: [Int], target: Int) -> Int { + for i in nums.indices { + if nums[i] == target { + return i + } + } + return -1 +} + +@main +enum _Array { + /* Driver Code */ + static func main() { + /* 配列を初期化 */ + let arr = Array(repeating: 0, count: 5) + print("配列 arr = \(arr)") + var nums = [1, 3, 2, 5, 4] + print("配列 nums = \(nums)") + + /* ランダムアクセス */ + let randomNum = randomAccess(nums: nums) + print("nums からランダムな要素を取得 \(randomNum)") + + /* 長さを拡張 */ + nums = extend(nums: nums, enlarge: 3) + print("配列の長さを 8 に拡張すると、nums = \(nums)") + + /* 要素を挿入する */ + insert(nums: &nums, num: 6, index: 3) + print("インデックス 3 に数値 6 を挿入すると、nums = \(nums)") + + /* 要素を削除 */ + remove(nums: &nums, index: 2) + print("インデックス 2 の要素を削除すると、nums = \(nums)") + + /* 配列を走査 */ + traverse(nums: nums) + + /* 要素を探索する */ + let index = find(nums: nums, target: 3) + print("nums 内で要素 3 を検索すると、インデックス = \(index)") + } +} diff --git a/ja/codes/swift/chapter_array_and_linkedlist/linked_list.swift b/ja/codes/swift/chapter_array_and_linkedlist/linked_list.swift new file mode 100644 index 000000000..1fa77a407 --- /dev/null +++ b/ja/codes/swift/chapter_array_and_linkedlist/linked_list.swift @@ -0,0 +1,90 @@ +/** + * File: linked_list.swift + * Created Time: 2023-01-08 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 連結リストでノード n0 の後ろにノード P を挿入する */ +func insert(n0: ListNode, P: ListNode) { + let n1 = n0.next + P.next = n1 + n0.next = P +} + +/* 連結リストでノード n0 の直後のノードを削除する */ +func remove(n0: ListNode) { + if n0.next == nil { + return + } + // n0 -> P -> n1 + let P = n0.next + let n1 = P?.next + n0.next = n1 +} + +/* 連結リスト内で index 番目のノードにアクセス */ +func access(head: ListNode, index: Int) -> ListNode? { + var head: ListNode? = head + for _ in 0 ..< index { + if head == nil { + return nil + } + head = head?.next + } + return head +} + +/* 連結リストで値が target の最初のノードを探す */ +func find(head: ListNode, target: Int) -> Int { + var head: ListNode? = head + var index = 0 + while head != nil { + if head?.val == target { + return index + } + head = head?.next + index += 1 + } + return -1 +} + +@main +enum LinkedList { + /* Driver Code */ + static func main() { + /* 連結リストを初期化 */ + // 各ノードを初期化 + let n0 = ListNode(x: 1) + let n1 = ListNode(x: 3) + let n2 = ListNode(x: 2) + let n3 = ListNode(x: 5) + let n4 = ListNode(x: 4) + // ノード間の参照を構築する + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + print("初期化した連結リストは") + PrintUtil.printLinkedList(head: n0) + + /* ノードを挿入 */ + insert(n0: n0, P: ListNode(x: 0)) + print("ノード挿入後の連結リストは") + PrintUtil.printLinkedList(head: n0) + + /* ノードを削除 */ + remove(n0: n0) + print("ノード削除後の連結リストは") + PrintUtil.printLinkedList(head: n0) + + /* ノードにアクセス */ + let node = access(head: n0, index: 3) + print("連結リストのインデックス 3 にあるノードの値 = \(node!.val)") + + /* ノードを探索 */ + let index = find(head: n0, target: 2) + print("連結リスト内で値が 2 のノードのインデックス = \(index)") + } +} diff --git a/ja/codes/swift/chapter_array_and_linkedlist/list.swift b/ja/codes/swift/chapter_array_and_linkedlist/list.swift new file mode 100644 index 000000000..c75098cbd --- /dev/null +++ b/ja/codes/swift/chapter_array_and_linkedlist/list.swift @@ -0,0 +1,63 @@ +/** + * File: list.swift + * Created Time: 2023-01-08 + * Author: nuomi1 (nuomi1@qq.com) + */ + +@main +enum List { + /* Driver Code */ + static func main() { + /* リストを初期化 */ + var nums = [1, 3, 2, 5, 4] + print("リスト nums = \(nums)") + + /* 要素にアクセス */ + let num = nums[1] + print("インデックス 1 の要素にアクセスすると、num = \(num)") + + /* 要素を更新 */ + nums[1] = 0 + print("インデックス 1 の要素を 0 に更新すると、nums = \(nums)") + + /* リストを空にする */ + nums.removeAll() + print("リストを空にした後、nums = \(nums)") + + /* 末尾に要素を追加 */ + nums.append(1) + nums.append(3) + nums.append(2) + nums.append(5) + nums.append(4) + print("要素追加後、nums = \(nums)") + + /* 中間に要素を挿入 */ + nums.insert(6, at: 3) + print("インデックス 3 に数値 6 を挿入すると、nums = \(nums)") + + /* 要素を削除 */ + nums.remove(at: 3) + print("インデックス 3 の要素を削除すると、nums = \(nums)") + + /* インデックスでリストを走査 */ + var count = 0 + for i in nums.indices { + count += nums[i] + } + /* リスト要素を直接走査 */ + count = 0 + for x in nums { + count += x + } + + /* 2 つのリストを連結する */ + let nums1 = [6, 8, 7, 10, 9] + nums.append(contentsOf: nums1) + print("リスト nums1 を nums の後ろに連結すると、nums = \(nums)") + + /* リストをソート */ + nums.sort() + print("リストをソートした後、nums = \(nums)") + } +} diff --git a/ja/codes/swift/chapter_array_and_linkedlist/my_list.swift b/ja/codes/swift/chapter_array_and_linkedlist/my_list.swift new file mode 100644 index 000000000..59d6a040c --- /dev/null +++ b/ja/codes/swift/chapter_array_and_linkedlist/my_list.swift @@ -0,0 +1,146 @@ +/** + * File: my_list.swift + * Created Time: 2023-01-08 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* リストクラス */ +class MyList { + private var arr: [Int] // 配列(リスト要素を格納) + private var _capacity: Int // リスト容量 + private var _size: Int // リストの長さ(現在の要素数) + private let extendRatio: Int // リスト拡張時の増加倍率 + + /* コンストラクタ */ + init() { + _capacity = 10 + _size = 0 + extendRatio = 2 + arr = Array(repeating: 0, count: _capacity) + } + + /* リストの長さを取得(現在の要素数) */ + func size() -> Int { + _size + } + + /* リスト容量を取得する */ + func capacity() -> Int { + _capacity + } + + /* 要素にアクセス */ + func get(index: Int) -> Int { + // インデックスが範囲外ならエラーを投げる。以下同様 + if index < 0 || index >= size() { + fatalError("インデックスが範囲外") + } + return arr[index] + } + + /* 要素を更新 */ + func set(index: Int, num: Int) { + if index < 0 || index >= size() { + fatalError("インデックスが範囲外") + } + arr[index] = num + } + + /* 末尾に要素を追加 */ + func add(num: Int) { + // 要素数が容量を超えると、拡張機構が発動する + if size() == capacity() { + extendCapacity() + } + arr[size()] = num + // 要素数を更新 + _size += 1 + } + + /* 中間に要素を挿入 */ + func insert(index: Int, num: Int) { + if index < 0 || index >= size() { + fatalError("インデックスが範囲外") + } + // 要素数が容量を超えると、拡張機構が発動する + if size() == capacity() { + extendCapacity() + } + // index 以降の要素をすべて 1 つ後ろへずらす + for j in (index ..< size()).reversed() { + arr[j + 1] = arr[j] + } + arr[index] = num + // 要素数を更新 + _size += 1 + } + + /* 要素を削除 */ + @discardableResult + func remove(index: Int) -> Int { + if index < 0 || index >= size() { + fatalError("インデックスが範囲外") + } + let num = arr[index] + // インデックス index より後の要素をすべて 1 つ前に移動する + for j in index ..< (size() - 1) { + arr[j] = arr[j + 1] + } + // 要素数を更新 + _size -= 1 + // 削除された要素を返す + return num + } + + /* リストの拡張 */ + func extendCapacity() { + // 元の配列の extendRatio 倍の長さを持つ新しい配列を作成し、元の配列をコピーする + arr = arr + Array(repeating: 0, count: capacity() * (extendRatio - 1)) + // リストの容量を更新 + _capacity = arr.count + } + + /* リストを配列に変換する */ + func toArray() -> [Int] { + Array(arr.prefix(size())) + } +} + +@main +enum _MyList { + /* Driver Code */ + static func main() { + /* リストを初期化 */ + let nums = MyList() + /* 末尾に要素を追加 */ + nums.add(num: 1) + nums.add(num: 3) + nums.add(num: 2) + nums.add(num: 5) + nums.add(num: 4) + print("リスト nums = \(nums.toArray()) 、容量 = \(nums.capacity()) 、長さ = \(nums.size())") + + /* 中間に要素を挿入 */ + nums.insert(index: 3, num: 6) + print("インデックス 3 に数値 6 を挿入すると、nums = \(nums.toArray())") + + /* 要素を削除 */ + nums.remove(index: 3) + print("インデックス 3 の要素を削除すると、nums = \(nums.toArray())") + + /* 要素にアクセス */ + let num = nums.get(index: 1) + print("インデックス 1 の要素にアクセスすると、num = \(num)") + + /* 要素を更新 */ + nums.set(index: 1, num: 0) + print("インデックス 1 の要素を 0 に更新すると、nums = \(nums.toArray())") + + /* 拡張機構をテストする */ + for i in 0 ..< 10 { + // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する + nums.add(num: i) + } + print("拡張後のリスト nums = \(nums.toArray()) 、容量 = \(nums.capacity()) 、長さ = \(nums.size())") + } +} diff --git a/ja/codes/swift/chapter_backtracking/n_queens.swift b/ja/codes/swift/chapter_backtracking/n_queens.swift new file mode 100644 index 000000000..eb044136e --- /dev/null +++ b/ja/codes/swift/chapter_backtracking/n_queens.swift @@ -0,0 +1,67 @@ +/** + * File: n_queens.swift + * Created Time: 2023-05-14 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* バックトラッキング:N クイーン */ +func backtrack(row: Int, n: Int, state: inout [[String]], res: inout [[[String]]], cols: inout [Bool], diags1: inout [Bool], diags2: inout [Bool]) { + // すべての行への配置が完了したら、解を記録する + if row == n { + res.append(state) + return + } + // すべての列を走査 + for col in 0 ..< n { + // このマスに対応する主対角線と副対角線を計算 + let diag1 = row - col + n - 1 + let diag2 = row + col + // 枝刈り:そのマスの列、主対角線、副対角線にクイーンがあってはならない + if !cols[col] && !diags1[diag1] && !diags2[diag2] { + // 試行:そのマスにクイーンを置く + state[row][col] = "Q" + cols[col] = true + diags1[diag1] = true + diags2[diag2] = true + // 次の行に配置する + backtrack(row: row + 1, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) + // 戻す:そのマスを空きマスに戻す + state[row][col] = "#" + cols[col] = false + diags1[diag1] = false + diags2[diag2] = false + } + } +} + +/* N クイーンを解く */ +func nQueens(n: Int) -> [[[String]]] { + // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す + var state = Array(repeating: Array(repeating: "#", count: n), count: n) + var cols = Array(repeating: false, count: n) // 列にクイーンがあるか記録 + var diags1 = Array(repeating: false, count: 2 * n - 1) // 主対角線にクイーンがあるかを記録 + var diags2 = Array(repeating: false, count: 2 * n - 1) // 副対角線にクイーンがあるかを記録 + var res: [[[String]]] = [] + + backtrack(row: 0, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) + + return res +} + +@main +enum NQueens { + /* Driver Code */ + static func main() { + let n = 4 + let res = nQueens(n: n) + + print("入力された盤面の縦横は \(n)") + print("クイーンの配置方法は全部で \(res.count) 通り") + for state in res { + print("--------------------") + for row in state { + print(row) + } + } + } +} diff --git a/ja/codes/swift/chapter_backtracking/permutations_i.swift b/ja/codes/swift/chapter_backtracking/permutations_i.swift new file mode 100644 index 000000000..16bff2163 --- /dev/null +++ b/ja/codes/swift/chapter_backtracking/permutations_i.swift @@ -0,0 +1,50 @@ +/** + * File: permutations_i.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* バックトラッキング:順列 I */ +func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { + // 状態の長さが要素数に等しければ、解を記録 + if state.count == choices.count { + res.append(state) + return + } + // すべての選択肢を走査 + for (i, choice) in choices.enumerated() { + // 枝刈り:要素の重複選択を許可しない + if !selected[i] { + // 試行: 選択を行い、状態を更新 + selected[i] = true + state.append(choice) + // 次の選択へ進む + backtrack(state: &state, choices: choices, selected: &selected, res: &res) + // バックトラック:選択を取り消し、前の状態に戻す + selected[i] = false + state.removeLast() + } + } +} + +/* 全順列 I */ +func permutationsI(nums: [Int]) -> [[Int]] { + var state: [Int] = [] + var selected = Array(repeating: false, count: nums.count) + var res: [[Int]] = [] + backtrack(state: &state, choices: nums, selected: &selected, res: &res) + return res +} + +@main +enum PermutationsI { + /* Driver Code */ + static func main() { + let nums = [1, 2, 3] + + let res = permutationsI(nums: nums) + + print("入力配列 nums = \(nums)") + print("すべての順列 res = \(res)") + } +} diff --git a/ja/codes/swift/chapter_backtracking/permutations_ii.swift b/ja/codes/swift/chapter_backtracking/permutations_ii.swift new file mode 100644 index 000000000..5051a6fcb --- /dev/null +++ b/ja/codes/swift/chapter_backtracking/permutations_ii.swift @@ -0,0 +1,52 @@ +/** + * File: permutations_ii.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* バックトラッキング:順列 II */ +func backtrack(state: inout [Int], choices: [Int], selected: inout [Bool], res: inout [[Int]]) { + // 状態の長さが要素数に等しければ、解を記録 + if state.count == choices.count { + res.append(state) + return + } + // すべての選択肢を走査 + var duplicated: Set = [] + for (i, choice) in choices.enumerated() { + // 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない + if !selected[i], !duplicated.contains(choice) { + // 試行: 選択を行い、状態を更新 + duplicated.insert(choice) // 選択済みの要素値を記録 + selected[i] = true + state.append(choice) + // 次の選択へ進む + backtrack(state: &state, choices: choices, selected: &selected, res: &res) + // バックトラック:選択を取り消し、前の状態に戻す + selected[i] = false + state.removeLast() + } + } +} + +/* 全順列 II */ +func permutationsII(nums: [Int]) -> [[Int]] { + var state: [Int] = [] + var selected = Array(repeating: false, count: nums.count) + var res: [[Int]] = [] + backtrack(state: &state, choices: nums, selected: &selected, res: &res) + return res +} + +@main +enum PermutationsII { + /* Driver Code */ + static func main() { + let nums = [1, 2, 3] + + let res = permutationsII(nums: nums) + + print("入力配列 nums = \(nums)") + print("すべての順列 res = \(res)") + } +} diff --git a/ja/codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift b/ja/codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift new file mode 100644 index 000000000..79ed77ca8 --- /dev/null +++ b/ja/codes/swift/chapter_backtracking/preorder_traversal_i_compact.swift @@ -0,0 +1,43 @@ +/** + * File: preorder_traversal_i_compact.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +var res: [TreeNode] = [] + +/* 前順走査:例題 1 */ +func preOrder(root: TreeNode?) { + guard let root = root else { + return + } + if root.val == 7 { + // 解を記録 + res.append(root) + } + preOrder(root: root.left) + preOrder(root: root.right) +} + +@main +enum PreorderTraversalICompact { + /* Driver Code */ + static func main() { + let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) + print("\n二分木を初期化") + PrintUtil.printTree(root: root) + + // 先行順走査 + res = [] + preOrder(root: root) + + print("\n値が 7 のすべてのノードを出力") + var vals: [Int] = [] + for node in res { + vals.append(node.val) + } + print(vals) + } +} diff --git a/ja/codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift b/ja/codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift new file mode 100644 index 000000000..9fc071591 --- /dev/null +++ b/ja/codes/swift/chapter_backtracking/preorder_traversal_ii_compact.swift @@ -0,0 +1,51 @@ +/** + * File: preorder_traversal_ii_compact.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +var path: [TreeNode] = [] +var res: [[TreeNode]] = [] + +/* 前順走査:例題 2 */ +func preOrder(root: TreeNode?) { + guard let root = root else { + return + } + // 試す + path.append(root) + if root.val == 7 { + // 解を記録 + res.append(path) + } + preOrder(root: root.left) + preOrder(root: root.right) + // バックトラック + path.removeLast() +} + +@main +enum PreorderTraversalIICompact { + /* Driver Code */ + static func main() { + let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) + print("\n二分木を初期化") + PrintUtil.printTree(root: root) + + // 先行順走査 + path = [] + res = [] + preOrder(root: root) + + print("\n根ノードからノード 7 までのすべての経路を出力") + for path in res { + var vals: [Int] = [] + for node in path { + vals.append(node.val) + } + print(vals) + } + } +} diff --git a/ja/codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift b/ja/codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift new file mode 100644 index 000000000..56671c16b --- /dev/null +++ b/ja/codes/swift/chapter_backtracking/preorder_traversal_iii_compact.swift @@ -0,0 +1,52 @@ +/** + * File: preorder_traversal_iii_compact.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +var path: [TreeNode] = [] +var res: [[TreeNode]] = [] + +/* 前順走査:例題 3 */ +func preOrder(root: TreeNode?) { + // 枝刈り + guard let root = root, root.val != 3 else { + return + } + // 試す + path.append(root) + if root.val == 7 { + // 解を記録 + res.append(path) + } + preOrder(root: root.left) + preOrder(root: root.right) + // バックトラック + path.removeLast() +} + +@main +enum PreorderTraversalIIICompact { + /* Driver Code */ + static func main() { + let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) + print("\n二分木を初期化") + PrintUtil.printTree(root: root) + + // 先行順走査 + path = [] + res = [] + preOrder(root: root) + + print("\n根ノードからノード 7 までのすべての経路を出力し、経路に値が 3 のノードを含まない") + for path in res { + var vals: [Int] = [] + for node in path { + vals.append(node.val) + } + print(vals) + } + } +} diff --git a/ja/codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift b/ja/codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift new file mode 100644 index 000000000..dd00defe4 --- /dev/null +++ b/ja/codes/swift/chapter_backtracking/preorder_traversal_iii_template.swift @@ -0,0 +1,76 @@ +/** + * File: preorder_traversal_iii_template.swift + * Created Time: 2023-04-30 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 現在の状態が解かどうかを判定 */ +func isSolution(state: [TreeNode]) -> Bool { + !state.isEmpty && state.last!.val == 7 +} + +/* 解を記録 */ +func recordSolution(state: [TreeNode], res: inout [[TreeNode]]) { + res.append(state) +} + +/* 現在の状態で、この選択が有効かどうかを判定 */ +func isValid(state: [TreeNode], choice: TreeNode?) -> Bool { + choice != nil && choice!.val != 3 +} + +/* 状態を更新 */ +func makeChoice(state: inout [TreeNode], choice: TreeNode) { + state.append(choice) +} + +/* 状態を元に戻す */ +func undoChoice(state: inout [TreeNode], choice: TreeNode) { + state.removeLast() +} + +/* バックトラッキング:例題 3 */ +func backtrack(state: inout [TreeNode], choices: [TreeNode], res: inout [[TreeNode]]) { + // 解かどうかを確認 + if isSolution(state: state) { + recordSolution(state: state, res: &res) + } + // すべての選択肢を走査 + for choice in choices { + // 枝刈り:選択が妥当かを確認する + if isValid(state: state, choice: choice) { + // 試行: 選択を行い、状態を更新 + makeChoice(state: &state, choice: choice) + // 次の選択へ進む + backtrack(state: &state, choices: [choice.left, choice.right].compactMap { $0 }, res: &res) + // バックトラック:選択を取り消し、前の状態に戻す + undoChoice(state: &state, choice: choice) + } + } +} + +@main +enum PreorderTraversalIIITemplate { + /* Driver Code */ + static func main() { + let root = TreeNode.listToTree(arr: [1, 7, 3, 4, 5, 6, 7]) + print("\n二分木を初期化") + PrintUtil.printTree(root: root) + + // バックトラッキング法 + var state: [TreeNode] = [] + var res: [[TreeNode]] = [] + backtrack(state: &state, choices: [root].compactMap { $0 }, res: &res) + + print("\n根ノードからノード 7 までのすべての経路を出力し、経路に値が 3 のノードを含まない") + for path in res { + var vals: [Int] = [] + for node in path { + vals.append(node.val) + } + print(vals) + } + } +} diff --git a/ja/codes/swift/chapter_backtracking/subset_sum_i.swift b/ja/codes/swift/chapter_backtracking/subset_sum_i.swift new file mode 100644 index 000000000..7559a7677 --- /dev/null +++ b/ja/codes/swift/chapter_backtracking/subset_sum_i.swift @@ -0,0 +1,53 @@ +/** + * File: subset_sum_i.swift + * Created Time: 2023-07-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* バックトラッキング:部分和 I */ +func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { + // 部分集合の和が target に等しければ、解を記録 + if target == 0 { + res.append(state) + return + } + // すべての選択肢を走査 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + for i in choices.indices.dropFirst(start) { + // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため + if target - choices[i] < 0 { + break + } + // 試す:選択を行い、target と start を更新 + state.append(choices[i]) + // 次の選択へ進む + backtrack(state: &state, target: target - choices[i], choices: choices, start: i, res: &res) + // バックトラック:選択を取り消し、前の状態に戻す + state.removeLast() + } +} + +/* 部分和 I を解く */ +func subsetSumI(nums: [Int], target: Int) -> [[Int]] { + var state: [Int] = [] // 状態(部分集合) + let nums = nums.sorted() // nums をソート + let start = 0 // 開始点を走査 + var res: [[Int]] = [] // 結果リスト(部分集合のリスト) + backtrack(state: &state, target: target, choices: nums, start: start, res: &res) + return res +} + +@main +enum SubsetSumI { + /* Driver Code */ + static func main() { + let nums = [3, 4, 5] + let target = 9 + + let res = subsetSumI(nums: nums, target: target) + + print("入力配列 nums = \(nums), target = \(target)") + print("和が \(target) に等しいすべての部分集合 res = \(res)") + } +} diff --git a/ja/codes/swift/chapter_backtracking/subset_sum_i_naive.swift b/ja/codes/swift/chapter_backtracking/subset_sum_i_naive.swift new file mode 100644 index 000000000..e0de68adb --- /dev/null +++ b/ja/codes/swift/chapter_backtracking/subset_sum_i_naive.swift @@ -0,0 +1,51 @@ +/** + * File: subset_sum_i_naive.swift + * Created Time: 2023-07-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* バックトラッキング:部分和 I */ +func backtrack(state: inout [Int], target: Int, total: Int, choices: [Int], res: inout [[Int]]) { + // 部分集合の和が target に等しければ、解を記録 + if total == target { + res.append(state) + return + } + // すべての選択肢を走査 + for i in choices.indices { + // 枝刈り:部分和が target を超える場合はその選択をスキップする + if total + choices[i] > target { + continue + } + // 試行:選択を行い、要素と total を更新する + state.append(choices[i]) + // 次の選択へ進む + backtrack(state: &state, target: target, total: total + choices[i], choices: choices, res: &res) + // バックトラック:選択を取り消し、前の状態に戻す + state.removeLast() + } +} + +/* 部分和 I を解く(重複部分集合を含む) */ +func subsetSumINaive(nums: [Int], target: Int) -> [[Int]] { + var state: [Int] = [] // 状態(部分集合) + let total = 0 // 部分和 + var res: [[Int]] = [] // 結果リスト(部分集合のリスト) + backtrack(state: &state, target: target, total: total, choices: nums, res: &res) + return res +} + +@main +enum SubsetSumINaive { + /* Driver Code */ + static func main() { + let nums = [3, 4, 5] + let target = 9 + + let res = subsetSumINaive(nums: nums, target: target) + + print("入力配列 nums = \(nums), target = \(target)") + print("和が \(target) に等しいすべての部分集合 res = \(res)") + print("この方法の出力結果には重複した集合が含まれることに注意してください") + } +} diff --git a/ja/codes/swift/chapter_backtracking/subset_sum_ii.swift b/ja/codes/swift/chapter_backtracking/subset_sum_ii.swift new file mode 100644 index 000000000..6505440e9 --- /dev/null +++ b/ja/codes/swift/chapter_backtracking/subset_sum_ii.swift @@ -0,0 +1,58 @@ +/** + * File: subset_sum_ii.swift + * Created Time: 2023-07-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* バックトラッキング:部分和 II */ +func backtrack(state: inout [Int], target: Int, choices: [Int], start: Int, res: inout [[Int]]) { + // 部分集合の和が target に等しければ、解を記録 + if target == 0 { + res.append(state) + return + } + // すべての選択肢を走査 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける + for i in choices.indices.dropFirst(start) { + // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため + if target - choices[i] < 0 { + break + } + // 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする + if i > start, choices[i] == choices[i - 1] { + continue + } + // 試す:選択を行い、target と start を更新 + state.append(choices[i]) + // 次の選択へ進む + backtrack(state: &state, target: target - choices[i], choices: choices, start: i + 1, res: &res) + // バックトラック:選択を取り消し、前の状態に戻す + state.removeLast() + } +} + +/* 部分和 II を解く */ +func subsetSumII(nums: [Int], target: Int) -> [[Int]] { + var state: [Int] = [] // 状態(部分集合) + let nums = nums.sorted() // nums をソート + let start = 0 // 開始点を走査 + var res: [[Int]] = [] // 結果リスト(部分集合のリスト) + backtrack(state: &state, target: target, choices: nums, start: start, res: &res) + return res +} + +@main +enum SubsetSumII { + /* Driver Code */ + static func main() { + let nums = [4, 4, 5] + let target = 9 + + let res = subsetSumII(nums: nums, target: target) + + print("入力配列 nums = \(nums), target = \(target)") + print("和が \(target) に等しいすべての部分集合 res = \(res)") + } +} diff --git a/ja/codes/swift/chapter_computational_complexity/iteration.swift b/ja/codes/swift/chapter_computational_complexity/iteration.swift new file mode 100644 index 000000000..00705f3cd --- /dev/null +++ b/ja/codes/swift/chapter_computational_complexity/iteration.swift @@ -0,0 +1,75 @@ +/** + * File: iteration.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* for ループ */ +func forLoop(n: Int) -> Int { + var res = 0 + // 1, 2, ..., n-1, n を順に加算する + for i in 1 ... n { + res += i + } + return res +} + +/* while ループ */ +func whileLoop(n: Int) -> Int { + var res = 0 + var i = 1 // 条件変数を初期化する + // 1, 2, ..., n-1, n を順に加算する + while i <= n { + res += i + i += 1 // 条件変数を更新する + } + return res +} + +/* while ループ(2回更新) */ +func whileLoopII(n: Int) -> Int { + var res = 0 + var i = 1 // 条件変数を初期化する + // 1, 4, 10, ... を順に加算する + while i <= n { + res += i + // 条件変数を更新する + i += 1 + i *= 2 + } + return res +} + +/* 二重 for ループ */ +func nestedForLoop(n: Int) -> String { + var res = "" + // i = 1, 2, ..., n-1, n とループする + for i in 1 ... n { + // j = 1, 2, ..., n-1, n とループする + for j in 1 ... n { + res.append("(\(i), \(j)), ") + } + } + return res +} + +@main +enum Iteration { + /* Driver Code */ + static func main() { + let n = 5 + var res = 0 + + res = forLoop(n: n) + print("\nfor ループの合計結果 res = \(res)") + + res = whileLoop(n: n) + print("\nwhile ループの合計結果 res = \(res)") + + res = whileLoopII(n: n) + print("\nwhile ループ(2 回更新)の合計結果 res = \(res)") + + let resStr = nestedForLoop(n: n) + print("\n二重 for ループの走査結果 \(resStr)") + } +} diff --git a/ja/codes/swift/chapter_computational_complexity/recursion.swift b/ja/codes/swift/chapter_computational_complexity/recursion.swift new file mode 100644 index 000000000..a398d528d --- /dev/null +++ b/ja/codes/swift/chapter_computational_complexity/recursion.swift @@ -0,0 +1,79 @@ +/** + * File: recursion.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 再帰 */ +func recur(n: Int) -> Int { + // 終了条件 + if n == 1 { + return 1 + } + // 再帰:再帰呼び出し + let res = recur(n: n - 1) + // 帰りがけ:結果を返す + return n + res +} + +/* 反復で再帰を模擬する */ +func forLoopRecur(n: Int) -> Int { + // 明示的なスタックを使ってシステムコールスタックを模擬する + var stack: [Int] = [] + var res = 0 + // 再帰:再帰呼び出し + for i in (1 ... n).reversed() { + // 「スタックへのプッシュ」で「再帰」を模擬する + stack.append(i) + } + // 帰りがけ:結果を返す + while !stack.isEmpty { + // 「スタックから取り出す操作」で「帰り」をシミュレート + res += stack.removeLast() + } + // res = 1+2+3+...+n + return res +} + +/* 末尾再帰 */ +func tailRecur(n: Int, res: Int) -> Int { + // 終了条件 + if n == 0 { + return res + } + // 末尾再帰呼び出し + return tailRecur(n: n - 1, res: res + n) +} + +/* フィボナッチ数列:再帰 */ +func fib(n: Int) -> Int { + // 終了条件 f(1) = 0, f(2) = 1 + if n == 1 || n == 2 { + return n - 1 + } + // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す + let res = fib(n: n - 1) + fib(n: n - 2) + // 結果 f(n) を返す + return res +} + +@main +enum Recursion { + /* Driver Code */ + static func main() { + let n = 5 + var res = 0 + + res = recursion.recur(n: n) + print("\n再帰関数による総和結果 res = \(res)") + + res = recursion.forLoopRecur(n: n) + print("\n反復で再帰をシミュレートした総和結果 res = \(res)") + + res = recursion.tailRecur(n: n, res: 0) + print("\n末尾再帰関数による総和結果 res = \(res)") + + res = recursion.fib(n: n) + print("\nフィボナッチ数列の第 \(n) 項は \(res)") + } +} diff --git a/ja/codes/swift/chapter_computational_complexity/space_complexity.swift b/ja/codes/swift/chapter_computational_complexity/space_complexity.swift new file mode 100644 index 000000000..055b55c43 --- /dev/null +++ b/ja/codes/swift/chapter_computational_complexity/space_complexity.swift @@ -0,0 +1,98 @@ +/** + * File: space_complexity.swift + * Created Time: 2023-01-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 関数 */ +@discardableResult +func function() -> Int { + // 何らかの処理を行う + return 0 +} + +/* 定数階 */ +func constant(n: Int) { + // 定数、変数、オブジェクトは O(1) の空間を占める + let a = 0 + var b = 0 + let nums = Array(repeating: 0, count: 10000) + let node = ListNode(x: 0) + // ループ内の変数は O(1) の空間を占める + for _ in 0 ..< n { + let c = 0 + } + // ループ内の関数は O(1) の空間を占める + for _ in 0 ..< n { + function() + } +} + +/* 線形階 */ +func linear(n: Int) { + // 長さ n の配列は O(n) の空間を使用 + let nums = Array(repeating: 0, count: n) + // 長さ n のリストは O(n) の空間を使用 + let nodes = (0 ..< n).map { ListNode(x: $0) } + // 長さ n のハッシュテーブルは O(n) の空間を使用 + let map = Dictionary(uniqueKeysWithValues: (0 ..< n).map { ($0, "\($0)") }) +} + +/* 線形時間(再帰実装) */ +func linearRecur(n: Int) { + print("再帰 n = \(n)") + if n == 1 { + return + } + linearRecur(n: n - 1) +} + +/* 二乗階 */ +func quadratic(n: Int) { + // 二次元リストは O(n^2) の空間を使用 + let numList = Array(repeating: Array(repeating: 0, count: n), count: n) +} + +/* 二次時間(再帰実装) */ +@discardableResult +func quadraticRecur(n: Int) -> Int { + if n <= 0 { + return 0 + } + // 配列 nums の長さは n, n-1, ..., 2, 1 + let nums = Array(repeating: 0, count: n) + print("再帰 n = \(n) における nums の長さ = \(nums.count)") + return quadraticRecur(n: n - 1) +} + +/* 指数時間(完全二分木の構築) */ +func buildTree(n: Int) -> TreeNode? { + if n == 0 { + return nil + } + let root = TreeNode(x: 0) + root.left = buildTree(n: n - 1) + root.right = buildTree(n: n - 1) + return root +} + +@main +enum SpaceComplexity { + /* Driver Code */ + static func main() { + let n = 5 + // 定数階 + constant(n: n) + // 線形階 + linear(n: n) + linearRecur(n: n) + // 二乗階 + quadratic(n: n) + quadraticRecur(n: n) + // 指数オーダー + let root = buildTree(n: n) + PrintUtil.printTree(root: root) + } +} diff --git a/ja/codes/swift/chapter_computational_complexity/time_complexity.swift b/ja/codes/swift/chapter_computational_complexity/time_complexity.swift new file mode 100644 index 000000000..95b1cb5a6 --- /dev/null +++ b/ja/codes/swift/chapter_computational_complexity/time_complexity.swift @@ -0,0 +1,172 @@ +/** + * File: time_complexity.swift + * Created Time: 2022-12-26 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 定数階 */ +func constant(n: Int) -> Int { + var count = 0 + let size = 100_000 + for _ in 0 ..< size { + count += 1 + } + return count +} + +/* 線形階 */ +func linear(n: Int) -> Int { + var count = 0 + for _ in 0 ..< n { + count += 1 + } + return count +} + +/* 線形時間(配列を走査) */ +func arrayTraversal(nums: [Int]) -> Int { + var count = 0 + // ループ回数は配列長に比例する + for _ in nums { + count += 1 + } + return count +} + +/* 二乗階 */ +func quadratic(n: Int) -> Int { + var count = 0 + // ループ回数はデータサイズ n の二乗に比例する + for _ in 0 ..< n { + for _ in 0 ..< n { + count += 1 + } + } + return count +} + +/* 二次時間(バブルソート) */ +func bubbleSort(nums: inout [Int]) -> Int { + var count = 0 // カウンタ + // 外側のループ:未ソート区間は [0, i] + for i in nums.indices.dropFirst().reversed() { + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for j in 0 ..< i { + if nums[j] > nums[j + 1] { + // nums[j] と nums[j + 1] を交換 + let tmp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = tmp + count += 3 // 要素交換には 3 回の単位操作が含まれる + } + } + } + return count +} + +/* 指数時間(ループ実装) */ +func exponential(n: Int) -> Int { + var count = 0 + var base = 1 + // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する + for _ in 0 ..< n { + for _ in 0 ..< base { + count += 1 + } + base *= 2 + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count +} + +/* 指数時間(再帰実装) */ +func expRecur(n: Int) -> Int { + if n == 1 { + return 1 + } + return expRecur(n: n - 1) + expRecur(n: n - 1) + 1 +} + +/* 対数時間(ループ実装) */ +func logarithmic(n: Int) -> Int { + var count = 0 + var n = n + while n > 1 { + n = n / 2 + count += 1 + } + return count +} + +/* 対数時間(再帰実装) */ +func logRecur(n: Int) -> Int { + if n <= 1 { + return 0 + } + return logRecur(n: n / 2) + 1 +} + +/* 線形対数時間 */ +func linearLogRecur(n: Int) -> Int { + if n <= 1 { + return 1 + } + var count = linearLogRecur(n: n / 2) + linearLogRecur(n: n / 2) + for _ in stride(from: 0, to: n, by: 1) { + count += 1 + } + return count +} + +/* 階乗時間(再帰実装) */ +func factorialRecur(n: Int) -> Int { + if n == 0 { + return 1 + } + var count = 0 + // 1個から n 個に分裂 + for _ in 0 ..< n { + count += factorialRecur(n: n - 1) + } + return count +} + +@main +enum TimeComplexity { + /* Driver Code */ + static func main() { + // n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる + let n = 8 + print("入力データサイズ n = \(n)") + + var count = constant(n: n) + print("定数時間の操作回数 = \(count)") + + count = linear(n: n) + print("線形時間の操作回数 = \(count)") + count = arrayTraversal(nums: Array(repeating: 0, count: n)) + print("線形時間(配列の走査)の操作回数 = \(count)") + + count = quadratic(n: n) + print("二乗時間の操作回数 = \(count)") + var nums = Array(stride(from: n, to: 0, by: -1)) // [n,n-1,...,2,1] + count = bubbleSort(nums: &nums) + print("二乗時間(バブルソート)の操作回数 = \(count)") + + count = exponential(n: n) + print("指数時間(ループ実装)の操作回数 = \(count)") + count = expRecur(n: n) + print("指数時間(再帰実装)の操作回数 = \(count)") + + count = logarithmic(n: n) + print("対数時間(ループ実装)の操作回数 = \(count)") + count = logRecur(n: n) + print("対数時間(再帰実装)の操作回数 = \(count)") + + count = linearLogRecur(n: n) + print("線形対数時間(再帰実装)の操作回数 = \(count)") + + count = factorialRecur(n: n) + print("階乗時間(再帰実装)の操作回数 = \(count)") + } +} diff --git a/ja/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift b/ja/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift new file mode 100644 index 000000000..f50eac633 --- /dev/null +++ b/ja/codes/swift/chapter_computational_complexity/worst_best_time_complexity.swift @@ -0,0 +1,40 @@ +/** + * File: worst_best_time_complexity.swift + * Created Time: 2022-12-26 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 */ +func randomNumbers(n: Int) -> [Int] { + // 配列 nums = { 1, 2, 3, ..., n } を生成 + var nums = Array(1 ... n) + // 配列要素をランダムにシャッフル + nums.shuffle() + return nums +} + +/* 配列 nums 内で数値 1 のインデックスを探す */ +func findOne(nums: [Int]) -> Int { + for i in nums.indices { + // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる + // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる + if nums[i] == 1 { + return i + } + } + return -1 +} + +@main +enum WorstBestTimeComplexity { + /* Driver Code */ + static func main() { + for _ in 0 ..< 10 { + let n = 100 + let nums = randomNumbers(n: n) + let index = findOne(nums: nums) + print("配列 [ 1, 2, ..., n ] をシャッフルした後 = \(nums)") + print("数値 1 のインデックスは \(index)") + } + } +} diff --git a/ja/codes/swift/chapter_divide_and_conquer/binary_search_recur.swift b/ja/codes/swift/chapter_divide_and_conquer/binary_search_recur.swift new file mode 100644 index 000000000..b9602017c --- /dev/null +++ b/ja/codes/swift/chapter_divide_and_conquer/binary_search_recur.swift @@ -0,0 +1,44 @@ +/** + * File: binary_search_recur.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 二分探索:問題 f(i, j) */ +func dfs(nums: [Int], target: Int, i: Int, j: Int) -> Int { + // 区間が空なら対象要素は存在しないので -1 を返す + if i > j { + return -1 + } + // 中点インデックス m を計算 + let m = (i + j) / 2 + if nums[m] < target { + // 部分問題 f(m+1, j) を再帰的に解く + return dfs(nums: nums, target: target, i: m + 1, j: j) + } else if nums[m] > target { + // 部分問題 f(i, m-1) を再帰的に解く + return dfs(nums: nums, target: target, i: i, j: m - 1) + } else { + // 目標要素が見つかったらそのインデックスを返す + return m + } +} + +/* 二分探索 */ +func binarySearch(nums: [Int], target: Int) -> Int { + // 問題 f(0, n-1) を解く + dfs(nums: nums, target: target, i: nums.startIndex, j: nums.endIndex - 1) +} + +@main +enum BinarySearchRecur { + /* Driver Code */ + static func main() { + let target = 6 + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + // 二分探索(両閉区間) + let index = binarySearch(nums: nums, target: target) + print("対象要素 6 のインデックス = \(index)") + } +} diff --git a/ja/codes/swift/chapter_divide_and_conquer/build_tree.swift b/ja/codes/swift/chapter_divide_and_conquer/build_tree.swift new file mode 100644 index 000000000..ccb0a1984 --- /dev/null +++ b/ja/codes/swift/chapter_divide_and_conquer/build_tree.swift @@ -0,0 +1,47 @@ +/** + * File: build_tree.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 二分木を構築:分割統治 */ +func dfs(preorder: [Int], inorderMap: [Int: Int], i: Int, l: Int, r: Int) -> TreeNode? { + // 部分木区間が空なら終了する + if r - l < 0 { + return nil + } + // ルートノードを初期化する + let root = TreeNode(x: preorder[i]) + // m を求めて左右部分木を分割する + let m = inorderMap[preorder[i]]! + // 部分問題:左部分木を構築する + root.left = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1, l: l, r: m - 1) + // 部分問題:右部分木を構築する + root.right = dfs(preorder: preorder, inorderMap: inorderMap, i: i + 1 + m - l, l: m + 1, r: r) + // 根ノードを返す + return root +} + +/* 二分木を構築 */ +func buildTree(preorder: [Int], inorder: [Int]) -> TreeNode? { + // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する + let inorderMap = inorder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset } + return dfs(preorder: preorder, inorderMap: inorderMap, i: inorder.startIndex, l: inorder.startIndex, r: inorder.endIndex - 1) +} + +@main +enum BuildTree { + /* Driver Code */ + static func main() { + let preorder = [3, 9, 2, 1, 7] + let inorder = [9, 3, 1, 2, 7] + print("前順走査 = \(preorder)") + print("中順走査 = \(inorder)") + + let root = buildTree(preorder: preorder, inorder: inorder) + print("構築した二分木は:") + PrintUtil.printTree(root: root) + } +} diff --git a/ja/codes/swift/chapter_divide_and_conquer/hanota.swift b/ja/codes/swift/chapter_divide_and_conquer/hanota.swift new file mode 100644 index 000000000..7db3e672f --- /dev/null +++ b/ja/codes/swift/chapter_divide_and_conquer/hanota.swift @@ -0,0 +1,58 @@ +/** + * File: hanota.swift + * Created Time: 2023-09-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 円盤を 1 枚移動 */ +func move(src: inout [Int], tar: inout [Int]) { + // src の上から円盤を1枚取り出す + let pan = src.popLast()! + // 円盤を tar の上に置く + tar.append(pan) +} + +/* ハノイの塔の問題 f(i) を解く */ +func dfs(i: Int, src: inout [Int], buf: inout [Int], tar: inout [Int]) { + // src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す + if i == 1 { + move(src: &src, tar: &tar) + return + } + // 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す + dfs(i: i - 1, src: &src, buf: &tar, tar: &buf) + // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す + move(src: &src, tar: &tar) + // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す + dfs(i: i - 1, src: &buf, buf: &src, tar: &tar) +} + +/* ハノイの塔を解く */ +func solveHanota(A: inout [Int], B: inout [Int], C: inout [Int]) { + let n = A.count + // リストの末尾が柱の上端に対応する + // src の上から n 個の円盤を、B を介して C に移動する + dfs(i: n, src: &A, buf: &B, tar: &C) +} + +@main +enum Hanota { + /* Driver Code */ + static func main() { + // リスト末尾が柱の頂上 + var A = [5, 4, 3, 2, 1] + var B: [Int] = [] + var C: [Int] = [] + print("初期状態:") + print("A = \(A)") + print("B = \(B)") + print("C = \(C)") + + solveHanota(A: &A, B: &B, C: &C) + + print("円盤の移動完了後:") + print("A = \(A)") + print("B = \(B)") + print("C = \(C)") + } +} diff --git a/ja/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift b/ja/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift new file mode 100644 index 000000000..e9bf2be09 --- /dev/null +++ b/ja/codes/swift/chapter_dynamic_programming/climbing_stairs_backtrack.swift @@ -0,0 +1,44 @@ +/** + * File: climbing_stairs_backtrack.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* バックトラッキング */ +func backtrack(choices: [Int], state: Int, n: Int, res: inout [Int]) { + // 第 n 段に到達したら、方法数を 1 増やす + if state == n { + res[0] += 1 + } + // すべての選択肢を走査 + for choice in choices { + // 枝刈り: 第 n 段を超えないようにする + if state + choice > n { + continue + } + // 試行: 選択を行い、状態を更新 + backtrack(choices: choices, state: state + choice, n: n, res: &res) + // バックトラック + } +} + +/* 階段登り:バックトラッキング */ +func climbingStairsBacktrack(n: Int) -> Int { + let choices = [1, 2] // 1 段または 2 段上ることを選べる + let state = 0 // 第 0 段から上り始める + var res: [Int] = [] + res.append(0) // res[0] を使って方法数を記録する + backtrack(choices: choices, state: state, n: n, res: &res) + return res[0] +} + +@main +enum ClimbingStairsBacktrack { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsBacktrack(n: n) + print("\(n) 段の階段を上る方法は全部で \(res) 通り") + } +} diff --git a/ja/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift b/ja/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift new file mode 100644 index 000000000..a7aa05e7c --- /dev/null +++ b/ja/codes/swift/chapter_dynamic_programming/climbing_stairs_constraint_dp.swift @@ -0,0 +1,36 @@ +/** + * File: climbing_stairs_constraint_dp.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 制約付き階段登り:動的計画法 */ +func climbingStairsConstraintDP(n: Int) -> Int { + if n == 1 || n == 2 { + return 1 + } + // 部分問題の解を保存するために dp テーブルを初期化 + var dp = Array(repeating: Array(repeating: 0, count: 3), count: n + 1) + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1][1] = 1 + dp[1][2] = 0 + dp[2][1] = 0 + dp[2][2] = 1 + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for i in 3 ... n { + dp[i][1] = dp[i - 1][2] + dp[i][2] = dp[i - 2][1] + dp[i - 2][2] + } + return dp[n][1] + dp[n][2] +} + +@main +enum ClimbingStairsConstraintDP { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsConstraintDP(n: n) + print("\(n) 段の階段を上る方法は全部で \(res) 通り") + } +} diff --git a/ja/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift b/ja/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift new file mode 100644 index 000000000..fcb43555e --- /dev/null +++ b/ja/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs.swift @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 検索 */ +func dfs(i: Int) -> Int { + // dp[1] と dp[2] は既知なので返す + if i == 1 || i == 2 { + return i + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i: i - 1) + dfs(i: i - 2) + return count +} + +/* 階段登り:探索 */ +func climbingStairsDFS(n: Int) -> Int { + dfs(i: n) +} + +@main +enum ClimbingStairsDFS { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsDFS(n: n) + print("\(n) 段の階段を上る方法は全部で \(res) 通り") + } +} diff --git a/ja/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift b/ja/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift new file mode 100644 index 000000000..0a87e7908 --- /dev/null +++ b/ja/codes/swift/chapter_dynamic_programming/climbing_stairs_dfs_mem.swift @@ -0,0 +1,40 @@ +/** + * File: climbing_stairs_dfs_mem.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* メモ化探索 */ +func dfs(i: Int, mem: inout [Int]) -> Int { + // dp[1] と dp[2] は既知なので返す + if i == 1 || i == 2 { + return i + } + // dp[i] の記録があれば、それをそのまま返す + if mem[i] != -1 { + return mem[i] + } + // dp[i] = dp[i-1] + dp[i-2] + let count = dfs(i: i - 1, mem: &mem) + dfs(i: i - 2, mem: &mem) + // dp[i] を記録する + mem[i] = count + return count +} + +/* 階段登り:メモ化探索 */ +func climbingStairsDFSMem(n: Int) -> Int { + // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す + var mem = Array(repeating: -1, count: n + 1) + return dfs(i: n, mem: &mem) +} + +@main +enum ClimbingStairsDFSMem { + /* Driver Code */ + static func main() { + let n = 9 + + let res = climbingStairsDFSMem(n: n) + print("\(n) 段の階段を上る方法は全部で \(res) 通り") + } +} diff --git a/ja/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift b/ja/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift new file mode 100644 index 000000000..589bc1fad --- /dev/null +++ b/ja/codes/swift/chapter_dynamic_programming/climbing_stairs_dp.swift @@ -0,0 +1,49 @@ +/** + * File: climbing_stairs_dp.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 階段登り:動的計画法 */ +func climbingStairsDP(n: Int) -> Int { + if n == 1 || n == 2 { + return n + } + // 部分問題の解を保存するために dp テーブルを初期化 + var dp = Array(repeating: 0, count: n + 1) + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1] = 1 + dp[2] = 2 + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for i in 3 ... n { + dp[i] = dp[i - 1] + dp[i - 2] + } + return dp[n] +} + +/* 階段登り:空間最適化した動的計画法 */ +func climbingStairsDPComp(n: Int) -> Int { + if n == 1 || n == 2 { + return n + } + var a = 1 + var b = 2 + for _ in 3 ... n { + (a, b) = (b, a + b) + } + return b +} + +@main +enum ClimbingStairsDP { + /* Driver Code */ + static func main() { + let n = 9 + + var res = climbingStairsDP(n: n) + print("\(n) 段の階段を上る方法は全部で \(res) 通り") + + res = climbingStairsDPComp(n: n) + print("\(n) 段の階段を上る方法は全部で \(res) 通り") + } +} diff --git a/ja/codes/swift/chapter_dynamic_programming/coin_change.swift b/ja/codes/swift/chapter_dynamic_programming/coin_change.swift new file mode 100644 index 000000000..f919f0521 --- /dev/null +++ b/ja/codes/swift/chapter_dynamic_programming/coin_change.swift @@ -0,0 +1,69 @@ +/** + * File: coin_change.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* コイン両替:動的計画法 */ +func coinChangeDP(coins: [Int], amt: Int) -> Int { + let n = coins.count + let MAX = amt + 1 + // dp テーブルを初期化 + var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) + // 状態遷移:先頭行と先頭列 + for a in 1 ... amt { + dp[0][a] = MAX + } + // 状態遷移: 残りの行と列 + for i in 1 ... n { + for a in 1 ... amt { + if coins[i - 1] > a { + // 目標金額を超えるなら硬貨 i は選ばない + dp[i][a] = dp[i - 1][a] + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) + } + } + } + return dp[n][amt] != MAX ? dp[n][amt] : -1 +} + +/* コイン交換:空間最適化後の動的計画法 */ +func coinChangeDPComp(coins: [Int], amt: Int) -> Int { + let n = coins.count + let MAX = amt + 1 + // dp テーブルを初期化 + var dp = Array(repeating: MAX, count: amt + 1) + dp[0] = 0 + // 状態遷移 + for i in 1 ... n { + for a in 1 ... amt { + if coins[i - 1] > a { + // 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a] + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) + } + } + } + return dp[amt] != MAX ? dp[amt] : -1 +} + +@main +enum CoinChange { + /* Driver Code */ + static func main() { + let coins = [1, 2, 5] + let amt = 4 + + // 動的計画法 + var res = coinChangeDP(coins: coins, amt: amt) + print("目標金額に必要な最小硬貨枚数は \(res)") + + // 空間最適化後の動的計画法 + res = coinChangeDPComp(coins: coins, amt: amt) + print("目標金額に必要な最小硬貨枚数は \(res)") + } +} diff --git a/ja/codes/swift/chapter_dynamic_programming/coin_change_ii.swift b/ja/codes/swift/chapter_dynamic_programming/coin_change_ii.swift new file mode 100644 index 000000000..484182b6f --- /dev/null +++ b/ja/codes/swift/chapter_dynamic_programming/coin_change_ii.swift @@ -0,0 +1,67 @@ +/** + * File: coin_change_ii.swift + * Created Time: 2023-07-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* コイン両替 II:動的計画法 */ +func coinChangeIIDP(coins: [Int], amt: Int) -> Int { + let n = coins.count + // dp テーブルを初期化 + var dp = Array(repeating: Array(repeating: 0, count: amt + 1), count: n + 1) + // 先頭列を初期化する + for i in 0 ... n { + dp[i][0] = 1 + } + // 状態遷移 + for i in 1 ... n { + for a in 1 ... amt { + if coins[i - 1] > a { + // 目標金額を超えるなら硬貨 i は選ばない + dp[i][a] = dp[i - 1][a] + } else { + // コイン i を選ばない場合と選ぶ場合の和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] + } + } + } + return dp[n][amt] +} + +/* コイン両替 II:空間最適化した動的計画法 */ +func coinChangeIIDPComp(coins: [Int], amt: Int) -> Int { + let n = coins.count + // dp テーブルを初期化 + var dp = Array(repeating: 0, count: amt + 1) + dp[0] = 1 + // 状態遷移 + for i in 1 ... n { + for a in 1 ... amt { + if coins[i - 1] > a { + // 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a] + } else { + // コイン i を選ばない場合と選ぶ場合の和 + dp[a] = dp[a] + dp[a - coins[i - 1]] + } + } + } + return dp[amt] +} + +@main +enum CoinChangeII { + /* Driver Code */ + static func main() { + let coins = [1, 2, 5] + let amt = 5 + + // 動的計画法 + var res = coinChangeIIDP(coins: coins, amt: amt) + print("目標金額を作る硬貨の組み合わせ数は \(res)") + + // 空間最適化後の動的計画法 + res = coinChangeIIDPComp(coins: coins, amt: amt) + print("目標金額を作る硬貨の組み合わせ数は \(res)") + } +} diff --git a/ja/codes/swift/chapter_dynamic_programming/edit_distance.swift b/ja/codes/swift/chapter_dynamic_programming/edit_distance.swift new file mode 100644 index 000000000..6695fc78b --- /dev/null +++ b/ja/codes/swift/chapter_dynamic_programming/edit_distance.swift @@ -0,0 +1,147 @@ +/** + * File: edit_distance.swift + * Created Time: 2023-07-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 編集距離:総当たり探索 */ +func editDistanceDFS(s: String, t: String, i: Int, j: Int) -> Int { + // s と t がともに空なら 0 を返す + if i == 0, j == 0 { + return 0 + } + // s が空なら t の長さを返す + if i == 0 { + return j + } + // t が空なら s の長さを返す + if j == 0 { + return i + } + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + } + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) + let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) + let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + // 最小編集回数を返す + return min(min(insert, delete), replace) + 1 +} + +/* 編集距離:メモ化探索 */ +func editDistanceDFSMem(s: String, t: String, mem: inout [[Int]], i: Int, j: Int) -> Int { + // s と t がともに空なら 0 を返す + if i == 0, j == 0 { + return 0 + } + // s が空なら t の長さを返す + if i == 0 { + return j + } + // t が空なら s の長さを返す + if j == 0 { + return i + } + // 記録済みなら、それをそのまま返す + if mem[i][j] != -1 { + return mem[i][j] + } + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + return editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + } + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + let insert = editDistanceDFS(s: s, t: t, i: i, j: j - 1) + let delete = editDistanceDFS(s: s, t: t, i: i - 1, j: j) + let replace = editDistanceDFS(s: s, t: t, i: i - 1, j: j - 1) + // 最小編集回数を記録して返す + mem[i][j] = min(min(insert, delete), replace) + 1 + return mem[i][j] +} + +/* 編集距離:動的計画法 */ +func editDistanceDP(s: String, t: String) -> Int { + let n = s.utf8CString.count + let m = t.utf8CString.count + var dp = Array(repeating: Array(repeating: 0, count: m + 1), count: n + 1) + // 状態遷移:先頭行と先頭列 + for i in 1 ... n { + dp[i][0] = i + } + for j in 1 ... m { + dp[0][j] = j + } + // 状態遷移: 残りの行と列 + for i in 1 ... n { + for j in 1 ... m { + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[i][j] = dp[i - 1][j - 1] + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1 + } + } + } + return dp[n][m] +} + +/* 編集距離:空間最適化した動的計画法 */ +func editDistanceDPComp(s: String, t: String) -> Int { + let n = s.utf8CString.count + let m = t.utf8CString.count + var dp = Array(repeating: 0, count: m + 1) + // 状態遷移:先頭行 + for j in 1 ... m { + dp[j] = j + } + // 状態遷移:残りの行 + for i in 1 ... n { + // 状態遷移:先頭列 + var leftup = dp[0] // dp[i-1, j-1] を一時保存する + dp[0] = i + // 状態遷移:残りの列 + for j in 1 ... m { + let temp = dp[j] + if s.utf8CString[i - 1] == t.utf8CString[j - 1] { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[j] = leftup + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1 + } + leftup = temp // 次の反復の dp[i-1, j-1] に更新する + } + } + return dp[m] +} + +@main +enum EditDistance { + /* Driver Code */ + static func main() { + let s = "bag" + let t = "pack" + let n = s.utf8CString.count + let m = t.utf8CString.count + + // 全探索 + var res = editDistanceDFS(s: s, t: t, i: n, j: m) + print("\(s) を \(t) に変更するには最小で \(res) 回の編集が必要") + + // メモ化探索 + var mem = Array(repeating: Array(repeating: -1, count: m + 1), count: n + 1) + res = editDistanceDFSMem(s: s, t: t, mem: &mem, i: n, j: m) + print("\(s) を \(t) に変更するには最小で \(res) 回の編集が必要") + + // 動的計画法 + res = editDistanceDP(s: s, t: t) + print("\(s) を \(t) に変更するには最小で \(res) 回の編集が必要") + + // 空間最適化後の動的計画法 + res = editDistanceDPComp(s: s, t: t) + print("\(s) を \(t) に変更するには最小で \(res) 回の編集が必要") + } +} diff --git a/ja/codes/swift/chapter_dynamic_programming/knapsack.swift b/ja/codes/swift/chapter_dynamic_programming/knapsack.swift new file mode 100644 index 000000000..d3fcdcc5c --- /dev/null +++ b/ja/codes/swift/chapter_dynamic_programming/knapsack.swift @@ -0,0 +1,110 @@ +/** + * File: knapsack.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 0-1 ナップサック:総当たり探索 */ +func knapsackDFS(wgt: [Int], val: [Int], i: Int, c: Int) -> Int { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if i == 0 || c == 0 { + return 0 + } + // ナップサック容量を超える場合は、入れない選択しかできない + if wgt[i - 1] > c { + return knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + let no = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c) + let yes = knapsackDFS(wgt: wgt, val: val, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] + // 2つの案のうち価値が大きいほうを返す + return max(no, yes) +} + +/* 0-1 ナップサック:メモ化探索 */ +func knapsackDFSMem(wgt: [Int], val: [Int], mem: inout [[Int]], i: Int, c: Int) -> Int { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if i == 0 || c == 0 { + return 0 + } + // 既に記録があればそのまま返す + if mem[i][c] != -1 { + return mem[i][c] + } + // ナップサック容量を超える場合は、入れない選択しかできない + if wgt[i - 1] > c { + return knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + let no = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c) + let yes = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: i - 1, c: c - wgt[i - 1]) + val[i - 1] + // 2 つの案のうち価値が大きい方を記録して返す + mem[i][c] = max(no, yes) + return mem[i][c] +} + +/* 0-1 ナップサック:動的計画法 */ +func knapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // dp テーブルを初期化 + var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) + // 状態遷移 + for i in 1 ... n { + for c in 1 ... cap { + if wgt[i - 1] > c { + // ナップサック容量を超えるなら品物 i は選ばない + dp[i][c] = dp[i - 1][c] + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[n][cap] +} + +/* 0-1 ナップサック:空間最適化後の動的計画法 */ +func knapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // dp テーブルを初期化 + var dp = Array(repeating: 0, count: cap + 1) + // 状態遷移 + for i in 1 ... n { + // 逆順に走査する + for c in (1 ... cap).reversed() { + if wgt[i - 1] <= c { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[cap] +} + +@main +enum Knapsack { + /* Driver Code */ + static func main() { + let wgt = [10, 20, 30, 40, 50] + let val = [50, 120, 150, 210, 240] + let cap = 50 + let n = wgt.count + + // 全探索 + var res = knapsackDFS(wgt: wgt, val: val, i: n, c: cap) + print("ナップサック容量を超えない最大価値は \(res)") + + // メモ化探索 + var mem = Array(repeating: Array(repeating: -1, count: cap + 1), count: n + 1) + res = knapsackDFSMem(wgt: wgt, val: val, mem: &mem, i: n, c: cap) + print("ナップサック容量を超えない最大価値は \(res)") + + // 動的計画法 + res = knapsackDP(wgt: wgt, val: val, cap: cap) + print("ナップサック容量を超えない最大価値は \(res)") + + // 空間最適化後の動的計画法 + res = knapsackDPComp(wgt: wgt, val: val, cap: cap) + print("ナップサック容量を超えない最大価値は \(res)") + } +} diff --git a/ja/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift b/ja/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift new file mode 100644 index 000000000..ffa9e1d24 --- /dev/null +++ b/ja/codes/swift/chapter_dynamic_programming/min_cost_climbing_stairs_dp.swift @@ -0,0 +1,51 @@ +/** + * File: min_cost_climbing_stairs_dp.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 階段登りの最小コスト:動的計画法 */ +func minCostClimbingStairsDP(cost: [Int]) -> Int { + let n = cost.count - 1 + if n == 1 || n == 2 { + return cost[n] + } + // 部分問題の解を保存するために dp テーブルを初期化 + var dp = Array(repeating: 0, count: n + 1) + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1] = cost[1] + dp[2] = cost[2] + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for i in 3 ... n { + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] + } + return dp[n] +} + +/* 階段昇りの最小コスト:空間最適化後の動的計画法 */ +func minCostClimbingStairsDPComp(cost: [Int]) -> Int { + let n = cost.count - 1 + if n == 1 || n == 2 { + return cost[n] + } + var (a, b) = (cost[1], cost[2]) + for i in 3 ... n { + (a, b) = (b, min(a, b) + cost[i]) + } + return b +} + +@main +enum MinCostClimbingStairsDP { + /* Driver Code */ + static func main() { + let cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] + print("入力された階段のコスト一覧は \(cost)") + + var res = minCostClimbingStairsDP(cost: cost) + print("階段を上り切る最小コストは \(res)") + + res = minCostClimbingStairsDPComp(cost: cost) + print("階段を上り切る最小コストは \(res)") + } +} diff --git a/ja/codes/swift/chapter_dynamic_programming/min_path_sum.swift b/ja/codes/swift/chapter_dynamic_programming/min_path_sum.swift new file mode 100644 index 000000000..c2b495c9c --- /dev/null +++ b/ja/codes/swift/chapter_dynamic_programming/min_path_sum.swift @@ -0,0 +1,123 @@ +/** + * File: min_path_sum.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 最小経路和:全探索 */ +func minPathSumDFS(grid: [[Int]], i: Int, j: Int) -> Int { + // 左上のセルなら探索を終了する + if i == 0, j == 0 { + return grid[0][0] + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if i < 0 || j < 0 { + return .max + } + // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する + let up = minPathSumDFS(grid: grid, i: i - 1, j: j) + let left = minPathSumDFS(grid: grid, i: i, j: j - 1) + // 左上隅から (i, j) までの最小経路コストを返す + return min(left, up) + grid[i][j] +} + +/* 最小経路和:メモ化探索 */ +func minPathSumDFSMem(grid: [[Int]], mem: inout [[Int]], i: Int, j: Int) -> Int { + // 左上のセルなら探索を終了する + if i == 0, j == 0 { + return grid[0][0] + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if i < 0 || j < 0 { + return .max + } + // 既に記録があればそのまま返す + if mem[i][j] != -1 { + return mem[i][j] + } + // 左と上のセルからの最小経路コスト + let up = minPathSumDFSMem(grid: grid, mem: &mem, i: i - 1, j: j) + let left = minPathSumDFSMem(grid: grid, mem: &mem, i: i, j: j - 1) + // 左上から (i, j) までの最小経路コストを記録して返す + mem[i][j] = min(left, up) + grid[i][j] + return mem[i][j] +} + +/* 最小経路和:動的計画法 */ +func minPathSumDP(grid: [[Int]]) -> Int { + let n = grid.count + let m = grid[0].count + // dp テーブルを初期化 + var dp = Array(repeating: Array(repeating: 0, count: m), count: n) + dp[0][0] = grid[0][0] + // 状態遷移:先頭行 + for j in 1 ..< m { + dp[0][j] = dp[0][j - 1] + grid[0][j] + } + // 状態遷移:先頭列 + for i in 1 ..< n { + dp[i][0] = dp[i - 1][0] + grid[i][0] + } + // 状態遷移: 残りの行と列 + for i in 1 ..< n { + for j in 1 ..< m { + dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] + } + } + return dp[n - 1][m - 1] +} + +/* 最小経路和:空間最適化後の動的計画法 */ +func minPathSumDPComp(grid: [[Int]]) -> Int { + let n = grid.count + let m = grid[0].count + // dp テーブルを初期化 + var dp = Array(repeating: 0, count: m) + // 状態遷移:先頭行 + dp[0] = grid[0][0] + for j in 1 ..< m { + dp[j] = dp[j - 1] + grid[0][j] + } + // 状態遷移:残りの行 + for i in 1 ..< n { + // 状態遷移:先頭列 + dp[0] = dp[0] + grid[i][0] + // 状態遷移:残りの列 + for j in 1 ..< m { + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] + } + } + return dp[m - 1] +} + +@main +enum MinPathSum { + /* Driver Code */ + static func main() { + let grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], + ] + let n = grid.count + let m = grid[0].count + + // 全探索 + var res = minPathSumDFS(grid: grid, i: n - 1, j: m - 1) + print("左上から右下までの最小経路和は \(res)") + + // メモ化探索 + var mem = Array(repeating: Array(repeating: -1, count: m), count: n) + res = minPathSumDFSMem(grid: grid, mem: &mem, i: n - 1, j: m - 1) + print("左上から右下までの最小経路和は \(res)") + + // 動的計画法 + res = minPathSumDP(grid: grid) + print("左上から右下までの最小経路和は \(res)") + + // 空間最適化後の動的計画法 + res = minPathSumDPComp(grid: grid) + print("左上から右下までの最小経路和は \(res)") + } +} diff --git a/ja/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift b/ja/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift new file mode 100644 index 000000000..4f5a2f9cc --- /dev/null +++ b/ja/codes/swift/chapter_dynamic_programming/unbounded_knapsack.swift @@ -0,0 +1,63 @@ +/** + * File: unbounded_knapsack.swift + * Created Time: 2023-07-15 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 完全ナップサック問題:動的計画法 */ +func unboundedKnapsackDP(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // dp テーブルを初期化 + var dp = Array(repeating: Array(repeating: 0, count: cap + 1), count: n + 1) + // 状態遷移 + for i in 1 ... n { + for c in 1 ... cap { + if wgt[i - 1] > c { + // ナップサック容量を超えるなら品物 i は選ばない + dp[i][c] = dp[i - 1][c] + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[n][cap] +} + +/* 完全ナップサック問題:空間最適化後の動的計画法 */ +func unboundedKnapsackDPComp(wgt: [Int], val: [Int], cap: Int) -> Int { + let n = wgt.count + // dp テーブルを初期化 + var dp = Array(repeating: 0, count: cap + 1) + // 状態遷移 + for i in 1 ... n { + for c in 1 ... cap { + if wgt[i - 1] > c { + // ナップサック容量を超えるなら品物 i は選ばない + dp[c] = dp[c] + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + } + } + } + return dp[cap] +} + +@main +enum UnboundedKnapsack { + /* Driver Code */ + static func main() { + let wgt = [1, 2, 3] + let val = [5, 11, 15] + let cap = 4 + + // 動的計画法 + var res = unboundedKnapsackDP(wgt: wgt, val: val, cap: cap) + print("ナップサック容量を超えない最大価値は \(res)") + + // 空間最適化後の動的計画法 + res = unboundedKnapsackDPComp(wgt: wgt, val: val, cap: cap) + print("ナップサック容量を超えない最大価値は \(res)") + } +} diff --git a/ja/codes/swift/chapter_graph/graph_adjacency_list.swift b/ja/codes/swift/chapter_graph/graph_adjacency_list.swift new file mode 100644 index 000000000..882e30d0f --- /dev/null +++ b/ja/codes/swift/chapter_graph/graph_adjacency_list.swift @@ -0,0 +1,121 @@ +/** + * File: graph_adjacency_list.swift + * Created Time: 2023-02-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 隣接リストに基づく無向グラフクラス */ +public class GraphAdjList { + // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 + public private(set) var adjList: [Vertex: [Vertex]] + + /* コンストラクタ */ + public init(edges: [[Vertex]]) { + adjList = [:] + // すべての頂点と辺を追加 + for edge in edges { + addVertex(vet: edge[0]) + addVertex(vet: edge[1]) + addEdge(vet1: edge[0], vet2: edge[1]) + } + } + + /* 頂点数を取得 */ + public func size() -> Int { + adjList.count + } + + /* 辺を追加 */ + public func addEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("引数エラー") + } + // 辺 vet1 - vet2 を追加 + adjList[vet1]?.append(vet2) + adjList[vet2]?.append(vet1) + } + + /* 辺を削除 */ + public func removeEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("引数エラー") + } + // 辺 vet1 - vet2 を削除 + adjList[vet1]?.removeAll { $0 == vet2 } + adjList[vet2]?.removeAll { $0 == vet1 } + } + + /* 頂点を追加 */ + public func addVertex(vet: Vertex) { + if adjList[vet] != nil { + return + } + // 隣接リストに新しいリストを追加 + adjList[vet] = [] + } + + /* 頂点を削除 */ + public func removeVertex(vet: Vertex) { + if adjList[vet] == nil { + fatalError("引数エラー") + } + // 隣接リストから頂点 vet に対応するリストを削除 + adjList.removeValue(forKey: vet) + // 他の頂点のリストを走査し、vet を含むすべての辺を削除 + for key in adjList.keys { + adjList[key]?.removeAll { $0 == vet } + } + } + + /* 隣接リストを出力 */ + public func print() { + Swift.print("隣接リスト =") + for (vertex, list) in adjList { + let list = list.map { $0.val } + Swift.print("\(vertex.val): \(list),") + } + } +} + +#if !TARGET + +@main +enum GraphAdjacencyList { + /* Driver Code */ + static func main() { + /* 無向グラフを初期化 */ + let v = Vertex.valsToVets(vals: [1, 3, 2, 5, 4]) + let edges = [[v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]]] + let graph = GraphAdjList(edges: edges) + print("\n初期化後のグラフ") + graph.print() + + /* 辺を追加 */ + // 頂点 1, 2 は v[0], v[2] + graph.addEdge(vet1: v[0], vet2: v[2]) + print("\n辺 1-2 を追加後のグラフ") + graph.print() + + /* 辺を削除 */ + // 頂点 1, 3 は v[0], v[1] + graph.removeEdge(vet1: v[0], vet2: v[1]) + print("\n辺 1-3 を削除後のグラフ") + graph.print() + + /* 頂点を追加 */ + let v5 = Vertex(val: 6) + graph.addVertex(vet: v5) + print("\n頂点 6 を追加後のグラフ") + graph.print() + + /* 頂点を削除 */ + // 頂点 3 は v[1] + graph.removeVertex(vet: v[1]) + print("\n頂点 3 を削除後のグラフ") + graph.print() + } +} + +#endif diff --git a/ja/codes/swift/chapter_graph/graph_adjacency_list_target.swift b/ja/codes/swift/chapter_graph/graph_adjacency_list_target.swift new file mode 100644 index 000000000..882e30d0f --- /dev/null +++ b/ja/codes/swift/chapter_graph/graph_adjacency_list_target.swift @@ -0,0 +1,121 @@ +/** + * File: graph_adjacency_list.swift + * Created Time: 2023-02-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 隣接リストに基づく無向グラフクラス */ +public class GraphAdjList { + // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 + public private(set) var adjList: [Vertex: [Vertex]] + + /* コンストラクタ */ + public init(edges: [[Vertex]]) { + adjList = [:] + // すべての頂点と辺を追加 + for edge in edges { + addVertex(vet: edge[0]) + addVertex(vet: edge[1]) + addEdge(vet1: edge[0], vet2: edge[1]) + } + } + + /* 頂点数を取得 */ + public func size() -> Int { + adjList.count + } + + /* 辺を追加 */ + public func addEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("引数エラー") + } + // 辺 vet1 - vet2 を追加 + adjList[vet1]?.append(vet2) + adjList[vet2]?.append(vet1) + } + + /* 辺を削除 */ + public func removeEdge(vet1: Vertex, vet2: Vertex) { + if adjList[vet1] == nil || adjList[vet2] == nil || vet1 == vet2 { + fatalError("引数エラー") + } + // 辺 vet1 - vet2 を削除 + adjList[vet1]?.removeAll { $0 == vet2 } + adjList[vet2]?.removeAll { $0 == vet1 } + } + + /* 頂点を追加 */ + public func addVertex(vet: Vertex) { + if adjList[vet] != nil { + return + } + // 隣接リストに新しいリストを追加 + adjList[vet] = [] + } + + /* 頂点を削除 */ + public func removeVertex(vet: Vertex) { + if adjList[vet] == nil { + fatalError("引数エラー") + } + // 隣接リストから頂点 vet に対応するリストを削除 + adjList.removeValue(forKey: vet) + // 他の頂点のリストを走査し、vet を含むすべての辺を削除 + for key in adjList.keys { + adjList[key]?.removeAll { $0 == vet } + } + } + + /* 隣接リストを出力 */ + public func print() { + Swift.print("隣接リスト =") + for (vertex, list) in adjList { + let list = list.map { $0.val } + Swift.print("\(vertex.val): \(list),") + } + } +} + +#if !TARGET + +@main +enum GraphAdjacencyList { + /* Driver Code */ + static func main() { + /* 無向グラフを初期化 */ + let v = Vertex.valsToVets(vals: [1, 3, 2, 5, 4]) + let edges = [[v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[2], v[3]], [v[2], v[4]], [v[3], v[4]]] + let graph = GraphAdjList(edges: edges) + print("\n初期化後のグラフ") + graph.print() + + /* 辺を追加 */ + // 頂点 1, 2 は v[0], v[2] + graph.addEdge(vet1: v[0], vet2: v[2]) + print("\n辺 1-2 を追加後のグラフ") + graph.print() + + /* 辺を削除 */ + // 頂点 1, 3 は v[0], v[1] + graph.removeEdge(vet1: v[0], vet2: v[1]) + print("\n辺 1-3 を削除後のグラフ") + graph.print() + + /* 頂点を追加 */ + let v5 = Vertex(val: 6) + graph.addVertex(vet: v5) + print("\n頂点 6 を追加後のグラフ") + graph.print() + + /* 頂点を削除 */ + // 頂点 3 は v[1] + graph.removeVertex(vet: v[1]) + print("\n頂点 3 を削除後のグラフ") + graph.print() + } +} + +#endif diff --git a/ja/codes/swift/chapter_graph/graph_adjacency_matrix.swift b/ja/codes/swift/chapter_graph/graph_adjacency_matrix.swift new file mode 100644 index 000000000..7d4118f9f --- /dev/null +++ b/ja/codes/swift/chapter_graph/graph_adjacency_matrix.swift @@ -0,0 +1,130 @@ +/** + * File: graph_adjacency_matrix.swift + * Created Time: 2023-02-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 隣接行列に基づく無向グラフクラス */ +class GraphAdjMat { + private var vertices: [Int] // 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す + private var adjMat: [[Int]] // 隣接行列。行・列のインデックスは「頂点インデックス」に対応 + + /* コンストラクタ */ + init(vertices: [Int], edges: [[Int]]) { + self.vertices = [] + adjMat = [] + // 頂点を追加 + for val in vertices { + addVertex(val: val) + } + // 辺を追加 + // 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する + for e in edges { + addEdge(i: e[0], j: e[1]) + } + } + + /* 頂点数を取得 */ + func size() -> Int { + vertices.count + } + + /* 頂点を追加 */ + func addVertex(val: Int) { + let n = size() + // 頂点リストに新しい頂点の値を追加 + vertices.append(val) + // 隣接行列に 1 行追加 + let newRow = Array(repeating: 0, count: n) + adjMat.append(newRow) + // 隣接行列に 1 列追加 + for i in adjMat.indices { + adjMat[i].append(0) + } + } + + /* 頂点を削除 */ + func removeVertex(index: Int) { + if index >= size() { + fatalError("範囲外") + } + // 頂点リストから index の頂点を削除する + vertices.remove(at: index) + // 隣接行列で index 行を削除する + adjMat.remove(at: index) + // 隣接行列で index 列を削除する + for i in adjMat.indices { + adjMat[i].remove(at: index) + } + } + + /* 辺を追加 */ + // 引数 i, j は vertices の要素インデックスに対応する + func addEdge(i: Int, j: Int) { + // インデックスの範囲外と等値の処理 + if i < 0 || j < 0 || i >= size() || j >= size() || i == j { + fatalError("範囲外") + } + // 無向グラフでは、隣接行列は主対角線に関して対称、すなわち (i, j) == (j, i) を満たす + adjMat[i][j] = 1 + adjMat[j][i] = 1 + } + + /* 辺を削除 */ + // 引数 i, j は vertices の要素インデックスに対応する + func removeEdge(i: Int, j: Int) { + // インデックスの範囲外と等値の処理 + if i < 0 || j < 0 || i >= size() || j >= size() || i == j { + fatalError("範囲外") + } + adjMat[i][j] = 0 + adjMat[j][i] = 0 + } + + /* 隣接行列を出力 */ + func print() { + Swift.print("頂点リスト = ", terminator: "") + Swift.print(vertices) + Swift.print("隣接行列 =") + PrintUtil.printMatrix(matrix: adjMat) + } +} + +@main +enum GraphAdjacencyMatrix { + /* Driver Code */ + static func main() { + /* 無向グラフを初期化 */ + // edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意 + let vertices = [1, 3, 2, 5, 4] + let edges = [[0, 1], [1, 2], [2, 3], [0, 3], [2, 4], [3, 4]] + let graph = GraphAdjMat(vertices: vertices, edges: edges) + print("\n初期化後のグラフ") + graph.print() + + /* 辺を追加 */ + // 頂点 1, 2 のインデックスはそれぞれ 0, 2 + graph.addEdge(i: 0, j: 2) + print("\n辺 1-2 を追加後のグラフ") + graph.print() + + /* 辺を削除 */ + // 頂点 1, 3 のインデックスはそれぞれ 0, 1 + graph.removeEdge(i: 0, j: 1) + print("\n辺 1-3 を削除後のグラフ") + graph.print() + + /* 頂点を追加 */ + graph.addVertex(val: 6) + print("\n頂点 6 を追加後のグラフ") + graph.print() + + /* 頂点を削除 */ + // 頂点 3 のインデックスは 1 + graph.removeVertex(index: 1) + print("\n頂点 3 を削除後のグラフ") + graph.print() + } +} diff --git a/ja/codes/swift/chapter_graph/graph_bfs.swift b/ja/codes/swift/chapter_graph/graph_bfs.swift new file mode 100644 index 000000000..0da00012d --- /dev/null +++ b/ja/codes/swift/chapter_graph/graph_bfs.swift @@ -0,0 +1,56 @@ +/** + * File: graph_bfs.swift + * Created Time: 2023-02-21 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import graph_adjacency_list_target +import utils + +/* 幅優先探索 */ +// グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする +func graphBFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { + // 頂点の走査順序 + var res: [Vertex] = [] + // 訪問済み頂点を記録するためのハッシュ集合 + var visited: Set = [startVet] + // BFS の実装にキューを用いる + var que: [Vertex] = [startVet] + // 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す + while !que.isEmpty { + let vet = que.removeFirst() // 先頭の頂点をデキュー + res.append(vet) // 訪問した頂点を記録 + // この頂点のすべての隣接頂点を走査 + for adjVet in graph.adjList[vet] ?? [] { + if visited.contains(adjVet) { + continue // 訪問済みの頂点をスキップ + } + que.append(adjVet) // 未訪問の頂点のみをキューに追加 + visited.insert(adjVet) // この頂点を訪問済みにする + } + } + // 頂点の走査順を返す + return res +} + +@main +enum GraphBFS { + /* Driver Code */ + static func main() { + /* 無向グラフを初期化 */ + let v = Vertex.valsToVets(vals: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + let edges = [ + [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], [v[1], v[4]], + [v[2], v[5]], [v[3], v[4]], [v[3], v[6]], [v[4], v[5]], + [v[4], v[7]], [v[5], v[8]], [v[6], v[7]], [v[7], v[8]], + ] + let graph = GraphAdjList(edges: edges) + print("\n初期化後のグラフ") + graph.print() + + /* 幅優先探索 */ + let res = graphBFS(graph: graph, startVet: v[0]) + print("\n幅優先探索(BFS)の頂点列") + print(Vertex.vetsToVals(vets: res)) + } +} diff --git a/ja/codes/swift/chapter_graph/graph_dfs.swift b/ja/codes/swift/chapter_graph/graph_dfs.swift new file mode 100644 index 000000000..fb31c0038 --- /dev/null +++ b/ja/codes/swift/chapter_graph/graph_dfs.swift @@ -0,0 +1,54 @@ +/** + * File: graph_dfs.swift + * Created Time: 2023-02-21 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import graph_adjacency_list_target +import utils + +/* 深さ優先走査の補助関数 */ +func dfs(graph: GraphAdjList, visited: inout Set, res: inout [Vertex], vet: Vertex) { + res.append(vet) // 訪問した頂点を記録 + visited.insert(vet) // この頂点を訪問済みにする + // この頂点のすべての隣接頂点を走査 + for adjVet in graph.adjList[vet] ?? [] { + if visited.contains(adjVet) { + continue // 訪問済みの頂点をスキップ + } + // 隣接頂点を再帰的に訪問 + dfs(graph: graph, visited: &visited, res: &res, vet: adjVet) + } +} + +/* 深さ優先探索 */ +// グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする +func graphDFS(graph: GraphAdjList, startVet: Vertex) -> [Vertex] { + // 頂点の走査順序 + var res: [Vertex] = [] + // 訪問済み頂点を記録するためのハッシュ集合 + var visited: Set = [] + dfs(graph: graph, visited: &visited, res: &res, vet: startVet) + return res +} + +@main +enum GraphDFS { + /* Driver Code */ + static func main() { + /* 無向グラフを初期化 */ + let v = Vertex.valsToVets(vals: [0, 1, 2, 3, 4, 5, 6]) + let edges = [ + [v[0], v[1]], [v[0], v[3]], [v[1], v[2]], + [v[2], v[5]], [v[4], v[5]], [v[5], v[6]], + ] + let graph = GraphAdjList(edges: edges) + print("\n初期化後のグラフ") + graph.print() + + /* 深さ優先探索 */ + let res = graphDFS(graph: graph, startVet: v[0]) + print("\n深さ優先探索(DFS)の頂点列") + print(Vertex.vetsToVals(vets: res)) + } +} diff --git a/ja/codes/swift/chapter_greedy/coin_change_greedy.swift b/ja/codes/swift/chapter_greedy/coin_change_greedy.swift new file mode 100644 index 000000000..c57d0d621 --- /dev/null +++ b/ja/codes/swift/chapter_greedy/coin_change_greedy.swift @@ -0,0 +1,54 @@ +/** + * File: coin_change_greedy.swift + * Created Time: 2023-09-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* コイン交換:貪欲法 */ +func coinChangeGreedy(coins: [Int], amt: Int) -> Int { + // coins リストはソート済みと仮定する + var i = coins.count - 1 + var count = 0 + var amt = amt + // 残額がなくなるまで貪欲選択を繰り返す + while amt > 0 { + // 残額以下で最も近い硬貨を見つける + while i > 0 && coins[i] > amt { + i -= 1 + } + // coins[i] を選択する + amt -= coins[i] + count += 1 + } + // 実行可能な解が見つからなければ -1 を返す + return amt == 0 ? count : -1 +} + +@main +enum CoinChangeGreedy { + /* Driver Code */ + static func main() { + // 貪欲法:大域最適解を保証できる + var coins = [1, 5, 10, 20, 50, 100] + var amt = 186 + var res = coinChangeGreedy(coins: coins, amt: amt) + print("\ncoins = \(coins), amount = \(amt)") + print("\(amt) を作るのに必要な最小硬貨枚数は \(res)") + + // 貪欲法:大域最適解を保証できない + coins = [1, 20, 50] + amt = 60 + res = coinChangeGreedy(coins: coins, amt: amt) + print("\ncoins = \(coins), amount = \(amt)") + print("\(amt) を作るのに必要な最小硬貨枚数は \(res)") + print("実際に必要な最小枚数は 3、つまり 20 + 20 + 20") + + // 貪欲法:大域最適解を保証できない + coins = [1, 49, 50] + amt = 98 + res = coinChangeGreedy(coins: coins, amt: amt) + print("\ncoins = \(coins), amount = \(amt)") + print("\(amt) を作るのに必要な最小硬貨枚数は \(res)") + print("実際に必要な最小枚数は 2、つまり 49 + 49") + } +} diff --git a/ja/codes/swift/chapter_greedy/fractional_knapsack.swift b/ja/codes/swift/chapter_greedy/fractional_knapsack.swift new file mode 100644 index 000000000..0f693b964 --- /dev/null +++ b/ja/codes/swift/chapter_greedy/fractional_knapsack.swift @@ -0,0 +1,57 @@ +/** + * File: fractional_knapsack.swift + * Created Time: 2023-09-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 品物 */ +class Item { + var w: Int // 品物の重さ + var v: Int // 品物の価値 + + init(w: Int, v: Int) { + self.w = w + self.v = v + } +} + +/* 分数ナップサック:貪欲法 */ +func fractionalKnapsack(wgt: [Int], val: [Int], cap: Int) -> Double { + // 重さと価値の 2 属性を持つ品物リストを作成 + var items = zip(wgt, val).map { Item(w: $0, v: $1) } + // 単位価値 item.v / item.w の高い順にソートする + items.sort { -(Double($0.v) / Double($0.w)) < -(Double($1.v) / Double($1.w)) } + // 貪欲選択を繰り返す + var res = 0.0 + var cap = cap + for item in items { + if item.w <= cap { + // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる + res += Double(item.v) + cap -= item.w + } else { + // 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる + res += Double(item.v) / Double(item.w) * Double(cap) + // 残り容量がないため、ループを抜ける + break + } + } + return res +} + +@main +enum FractionalKnapsack { + /* Driver Code */ + static func main() { + // 品物の重さ + let wgt = [10, 20, 30, 40, 50] + // 品物の価値 + let val = [50, 120, 150, 210, 240] + // ナップサック容量 + let cap = 50 + + // 貪欲法 + let res = fractionalKnapsack(wgt: wgt, val: val, cap: cap) + print("ナップサック容量を超えない最大価値は \(res)") + } +} diff --git a/ja/codes/swift/chapter_greedy/max_capacity.swift b/ja/codes/swift/chapter_greedy/max_capacity.swift new file mode 100644 index 000000000..84c23fa48 --- /dev/null +++ b/ja/codes/swift/chapter_greedy/max_capacity.swift @@ -0,0 +1,38 @@ +/** + * File: max_capacity.swift + * Created Time: 2023-09-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 最大容量:貪欲法 */ +func maxCapacity(ht: [Int]) -> Int { + // i, j を初期化し、それぞれ配列の両端に置く + var i = ht.startIndex, j = ht.endIndex - 1 + // 初期の最大容量は 0 + var res = 0 + // 2 枚の板が出会うまで貪欲選択を繰り返す + while i < j { + // 最大容量を更新する + let cap = min(ht[i], ht[j]) * (j - i) + res = max(res, cap) + // 短い方を内側へ動かす + if ht[i] < ht[j] { + i += 1 + } else { + j -= 1 + } + } + return res +} + +@main +enum MaxCapacity { + /* Driver Code */ + static func main() { + let ht = [3, 8, 5, 2, 7, 7, 3, 4] + + // 貪欲法 + let res = maxCapacity(ht: ht) + print("最大容量は \(res)") + } +} diff --git a/ja/codes/swift/chapter_greedy/max_product_cutting.swift b/ja/codes/swift/chapter_greedy/max_product_cutting.swift new file mode 100644 index 000000000..d959eeed9 --- /dev/null +++ b/ja/codes/swift/chapter_greedy/max_product_cutting.swift @@ -0,0 +1,43 @@ +/** + * File: max_product_cutting.swift + * Created Time: 2023-09-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import Foundation + +func pow(_ x: Int, _ y: Int) -> Int { + Int(Double(truncating: pow(Decimal(x), y) as NSDecimalNumber)) +} + +/* 最大切断積:貪欲法 */ +func maxProductCutting(n: Int) -> Int { + // n <= 3 のときは、必ず 1 を切り出す + if n <= 3 { + return 1 * (n - 1) + } + // 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする + let a = n / 3 + let b = n % 3 + if b == 1 { + // 余りが 1 のときは、1 * 3 を 2 * 2 に変える + return pow(3, a - 1) * 2 * 2 + } + if b == 2 { + // 余りが 2 のときは、そのままにする + return pow(3, a) * 2 + } + // 余りが 0 のときは、そのままにする + return pow(3, a) +} + +@main +enum MaxProductCutting { + static func main() { + let n = 58 + + // 貪欲法 + let res = maxProductCutting(n: n) + print("最大分割積は \(res)") + } +} diff --git a/ja/codes/swift/chapter_hashing/array_hash_map.swift b/ja/codes/swift/chapter_hashing/array_hash_map.swift new file mode 100644 index 000000000..99e45fff5 --- /dev/null +++ b/ja/codes/swift/chapter_hashing/array_hash_map.swift @@ -0,0 +1,110 @@ +/** + * File: array_hash_map.swift + * Created Time: 2023-01-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 配列ベースのハッシュテーブル */ +class ArrayHashMap { + private var buckets: [Pair?] + + init() { + // 100 個のバケットを含む配列を初期化 + buckets = Array(repeating: nil, count: 100) + } + + /* ハッシュ関数 */ + private func hashFunc(key: Int) -> Int { + let index = key % 100 + return index + } + + /* 検索操作 */ + func get(key: Int) -> String? { + let index = hashFunc(key: key) + let pair = buckets[index] + return pair?.val + } + + /* 追加操作 */ + func put(key: Int, val: String) { + let pair = Pair(key: key, val: val) + let index = hashFunc(key: key) + buckets[index] = pair + } + + /* 削除操作 */ + func remove(key: Int) { + let index = hashFunc(key: key) + // nil に設定し、削除を表す + buckets[index] = nil + } + + /* すべてのキーと値のペアを取得 */ + func pairSet() -> [Pair] { + buckets.compactMap { $0 } + } + + /* すべてのキーを取得 */ + func keySet() -> [Int] { + buckets.compactMap { $0?.key } + } + + /* すべての値を取得 */ + func valueSet() -> [String] { + buckets.compactMap { $0?.val } + } + + /* ハッシュテーブルを出力 */ + func print() { + for pair in pairSet() { + Swift.print("\(pair.key) -> \(pair.val)") + } + } +} + +@main +enum _ArrayHashMap { + /* Driver Code */ + static func main() { + /* ハッシュテーブルを初期化 */ + let map = ArrayHashMap() + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.put(key: 12836, val: "シャオハー") + map.put(key: 15937, val: "シャオルオ") + map.put(key: 16750, val: "シャオスワン") + map.put(key: 13276, val: "シャオファー") + map.put(key: 10583, val: "シャオヤー") + print("\n追加完了後のハッシュテーブルは\nKey -> Value") + map.print() + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + let name = map.get(key: 15937)! + print("\n学籍番号 15937 を入力すると、名前 \(name) が見つかりました") + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + map.remove(key: 10583) + print("\n10583 を削除後のハッシュテーブルは\nKey -> Value") + map.print() + + /* ハッシュテーブルを走査 */ + print("\nキーと値の組 Key->Value を走査") + for pair in map.pairSet() { + print("\(pair.key) -> \(pair.val)") + } + print("\nキー Key を個別に走査") + for key in map.keySet() { + print(key) + } + print("\n値 Value を個別に走査") + for val in map.valueSet() { + print(val) + } + } +} diff --git a/ja/codes/swift/chapter_hashing/built_in_hash.swift b/ja/codes/swift/chapter_hashing/built_in_hash.swift new file mode 100644 index 000000000..76816c3c9 --- /dev/null +++ b/ja/codes/swift/chapter_hashing/built_in_hash.swift @@ -0,0 +1,37 @@ +/** + * File: built_in_hash.swift + * Created Time: 2023-07-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +@main +enum BuiltInHash { + /* Driver Code */ + static func main() { + let num = 3 + let hashNum = num.hashValue + print("整数 \(num) のハッシュ値は \(hashNum)") + + let bol = true + let hashBol = bol.hashValue + print("真偽値 \(bol) のハッシュ値は \(hashBol)") + + let dec = 3.14159 + let hashDec = dec.hashValue + print("小数 \(dec) のハッシュ値は \(hashDec)") + + let str = "Hello アルゴリズム" + let hashStr = str.hashValue + print("文字列 \(str) のハッシュ値は \(hashStr)") + + let arr = [AnyHashable(12836), AnyHashable("シャオハー")] + let hashTup = arr.hashValue + print("配列 \(arr) のハッシュ値は \(hashTup)") + + let obj = ListNode(x: 0) + let hashObj = obj.hashValue + print("ノードオブジェクト \(obj) のハッシュ値は \(hashObj)") + } +} diff --git a/ja/codes/swift/chapter_hashing/hash_map.swift b/ja/codes/swift/chapter_hashing/hash_map.swift new file mode 100644 index 000000000..fed0dc6ce --- /dev/null +++ b/ja/codes/swift/chapter_hashing/hash_map.swift @@ -0,0 +1,51 @@ +/** + * File: hash_map.swift + * Created Time: 2023-01-16 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +@main +enum HashMap { + /* Driver Code */ + static func main() { + /* ハッシュテーブルを初期化 */ + var map: [Int: String] = [:] + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map[12836] = "シャオハー" + map[15937] = "シャオルオ" + map[16750] = "シャオスワン" + map[13276] = "シャオファー" + map[10583] = "シャオヤー" + print("\n追加完了後のハッシュテーブルは\nKey -> Value") + PrintUtil.printHashMap(map: map) + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + let name = map[15937]! + print("\n学籍番号 15937 を入力すると、名前 \(name) が見つかりました") + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + map.removeValue(forKey: 10583) + print("\n10583 を削除後のハッシュテーブルは\nKey -> Value") + PrintUtil.printHashMap(map: map) + + /* ハッシュテーブルを走査 */ + print("\nキーと値の組 Key->Value を走査") + for (key, value) in map { + print("\(key) -> \(value)") + } + print("\nキー Key を個別に走査") + for key in map.keys { + print(key) + } + print("\n値 Value を個別に走査") + for value in map.values { + print(value) + } + } +} diff --git a/ja/codes/swift/chapter_hashing/hash_map_chaining.swift b/ja/codes/swift/chapter_hashing/hash_map_chaining.swift new file mode 100644 index 000000000..7064bfe0c --- /dev/null +++ b/ja/codes/swift/chapter_hashing/hash_map_chaining.swift @@ -0,0 +1,138 @@ +/** + * File: hash_map_chaining.swift + * Created Time: 2023-06-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* チェイン法ハッシュテーブル */ +class HashMapChaining { + var size: Int // キーと値のペア数 + var capacity: Int // ハッシュテーブル容量 + var loadThres: Double // リサイズを発動する負荷率のしきい値 + var extendRatio: Int // 拡張倍率 + var buckets: [[Pair]] // バケット配列 + + /* コンストラクタ */ + init() { + size = 0 + capacity = 4 + loadThres = 2.0 / 3.0 + extendRatio = 2 + buckets = Array(repeating: [], count: capacity) + } + + /* ハッシュ関数 */ + func hashFunc(key: Int) -> Int { + key % capacity + } + + /* 負荷率 */ + func loadFactor() -> Double { + Double(size) / Double(capacity) + } + + /* 検索操作 */ + func get(key: Int) -> String? { + let index = hashFunc(key: key) + let bucket = buckets[index] + // バケットを走査し、key が見つかれば対応する val を返す + for pair in bucket { + if pair.key == key { + return pair.val + } + } + // `key` が見つからなければ `nil` を返す + return nil + } + + /* 追加操作 */ + func put(key: Int, val: String) { + // 負荷率がしきい値を超えたら、リサイズを実行 + if loadFactor() > loadThres { + extend() + } + let index = hashFunc(key: key) + let bucket = buckets[index] + // バケットを走査し、指定した key が見つかれば対応する val を更新して返す + for pair in bucket { + if pair.key == key { + pair.val = val + return + } + } + // その key が存在しなければ、キーと値のペアを末尾に追加 + let pair = Pair(key: key, val: val) + buckets[index].append(pair) + size += 1 + } + + /* 削除操作 */ + func remove(key: Int) { + let index = hashFunc(key: key) + let bucket = buckets[index] + // バケットを走査してキーと値のペアを削除 + for (pairIndex, pair) in bucket.enumerated() { + if pair.key == key { + buckets[index].remove(at: pairIndex) + size -= 1 + break + } + } + } + + /* ハッシュテーブルを拡張 */ + func extend() { + // 元のハッシュテーブルを一時保存 + let bucketsTmp = buckets + // リサイズ後の新しいハッシュテーブルを初期化 + capacity *= extendRatio + buckets = Array(repeating: [], count: capacity) + size = 0 + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す + for bucket in bucketsTmp { + for pair in bucket { + put(key: pair.key, val: pair.val) + } + } + } + + /* ハッシュテーブルを出力 */ + func print() { + for bucket in buckets { + let res = bucket.map { "\($0.key) -> \($0.val)" } + Swift.print(res) + } + } +} + +@main +enum _HashMapChaining { + /* Driver Code */ + static func main() { + /* ハッシュテーブルを初期化 */ + let map = HashMapChaining() + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.put(key: 12836, val: "シャオハー") + map.put(key: 15937, val: "シャオルオ") + map.put(key: 16750, val: "シャオスワン") + map.put(key: 13276, val: "シャオファー") + map.put(key: 10583, val: "シャオヤー") + print("\n追加完了後のハッシュテーブルは\nKey -> Value") + map.print() + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + let name = map.get(key: 13276) + print("\n学籍番号 13276 を入力すると、名前 \(name!) が見つかりました") + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + map.remove(key: 12836) + print("\n12836 を削除後、ハッシュテーブルは\nKey -> Value") + map.print() + } +} diff --git a/ja/codes/swift/chapter_hashing/hash_map_open_addressing.swift b/ja/codes/swift/chapter_hashing/hash_map_open_addressing.swift new file mode 100644 index 000000000..a31556ce1 --- /dev/null +++ b/ja/codes/swift/chapter_hashing/hash_map_open_addressing.swift @@ -0,0 +1,164 @@ +/** + * File: hash_map_open_addressing.swift + * Created Time: 2023-06-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* オープンアドレス法ハッシュテーブル */ +class HashMapOpenAddressing { + var size: Int // キーと値のペア数 + var capacity: Int // ハッシュテーブル容量 + var loadThres: Double // リサイズを発動する負荷率のしきい値 + var extendRatio: Int // 拡張倍率 + var buckets: [Pair?] // バケット配列 + var TOMBSTONE: Pair // 削除済みマーク + + /* コンストラクタ */ + init() { + size = 0 + capacity = 4 + loadThres = 2.0 / 3.0 + extendRatio = 2 + buckets = Array(repeating: nil, count: capacity) + TOMBSTONE = Pair(key: -1, val: "-1") + } + + /* ハッシュ関数 */ + func hashFunc(key: Int) -> Int { + key % capacity + } + + /* 負荷率 */ + func loadFactor() -> Double { + Double(size) / Double(capacity) + } + + /* key に対応するバケットインデックスを探す */ + func findBucket(key: Int) -> Int { + var index = hashFunc(key: key) + var firstTombstone = -1 + // 線形プロービングを行い、空バケットに達したら終了 + while buckets[index] != nil { + // key が見つかったら、対応するバケットのインデックスを返す + if buckets[index]!.key == key { + // 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 + if firstTombstone != -1 { + buckets[firstTombstone] = buckets[index] + buckets[index] = TOMBSTONE + return firstTombstone // 移動後のバケットインデックスを返す + } + return index // バケットのインデックスを返す + } + // 最初に見つかった削除マークを記録 + if firstTombstone == -1 && buckets[index] == TOMBSTONE { + firstTombstone = index + } + // バケットのインデックスを計算し、末尾を越えたら先頭に戻る + index = (index + 1) % capacity + } + // key が存在しない場合は追加位置のインデックスを返す + return firstTombstone == -1 ? index : firstTombstone + } + + /* 検索操作 */ + func get(key: Int) -> String? { + // key に対応するバケットインデックスを探す + let index = findBucket(key: key) + // キーと値の組が見つかったら、対応する val を返す + if buckets[index] != nil, buckets[index] != TOMBSTONE { + return buckets[index]!.val + } + // キーと値の組が存在しなければ null を返す + return nil + } + + /* 追加操作 */ + func put(key: Int, val: String) { + // 負荷率がしきい値を超えたら、リサイズを実行 + if loadFactor() > loadThres { + extend() + } + // key に対応するバケットインデックスを探す + let index = findBucket(key: key) + // キーと値の組が見つかったら、val を上書きして返す + if buckets[index] != nil, buckets[index] != TOMBSTONE { + buckets[index]!.val = val + return + } + // キーと値の組が存在しない場合は、その組を追加する + buckets[index] = Pair(key: key, val: val) + size += 1 + } + + /* 削除操作 */ + func remove(key: Int) { + // key に対応するバケットインデックスを探す + let index = findBucket(key: key) + // キーと値の組が見つかったら、削除マーカーで上書きする + if buckets[index] != nil, buckets[index] != TOMBSTONE { + buckets[index] = TOMBSTONE + size -= 1 + } + } + + /* ハッシュテーブルを拡張 */ + func extend() { + // 元のハッシュテーブルを一時保存 + let bucketsTmp = buckets + // リサイズ後の新しいハッシュテーブルを初期化 + capacity *= extendRatio + buckets = Array(repeating: nil, count: capacity) + size = 0 + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す + for pair in bucketsTmp { + if let pair, pair != TOMBSTONE { + put(key: pair.key, val: pair.val) + } + } + } + + /* ハッシュテーブルを出力 */ + func print() { + for pair in buckets { + if pair == nil { + Swift.print("null") + } else if pair == TOMBSTONE { + Swift.print("TOMBSTONE") + } else { + Swift.print("\(pair!.key) -> \(pair!.val)") + } + } + } +} + +@main +enum _HashMapOpenAddressing { + /* Driver Code */ + static func main() { + /* ハッシュテーブルを初期化 */ + let map = HashMapOpenAddressing() + + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.put(key: 12836, val: "シャオハー") + map.put(key: 15937, val: "シャオルオ") + map.put(key: 16750, val: "シャオスワン") + map.put(key: 13276, val: "シャオファー") + map.put(key: 10583, val: "シャオヤー") + print("\n追加完了後のハッシュテーブルは\nKey -> Value") + map.print() + + /* 検索操作 */ + // キー key をハッシュテーブルに渡し、値 value を取得 + let name = map.get(key: 13276) + print("\n学籍番号 13276 を入力すると、名前 \(name!) が見つかりました") + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + map.remove(key: 16750) + print("\n16750 を削除後、ハッシュテーブルは\nKey -> Value") + map.print() + } +} diff --git a/ja/codes/swift/chapter_hashing/simple_hash.swift b/ja/codes/swift/chapter_hashing/simple_hash.swift new file mode 100644 index 000000000..b067519ff --- /dev/null +++ b/ja/codes/swift/chapter_hashing/simple_hash.swift @@ -0,0 +1,73 @@ +/** + * File: simple_hash.swift + * Created Time: 2023-07-01 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 加算ハッシュ */ +func addHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash = (hash + Int(scalar.value)) % MODULUS + } + } + return hash +} + +/* 乗算ハッシュ */ +func mulHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash = (31 * hash + Int(scalar.value)) % MODULUS + } + } + return hash +} + +/* XOR ハッシュ */ +func xorHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash ^= Int(scalar.value) + } + } + return hash & MODULUS +} + +/* 回転ハッシュ */ +func rotHash(key: String) -> Int { + var hash = 0 + let MODULUS = 1_000_000_007 + for c in key { + for scalar in c.unicodeScalars { + hash = ((hash << 4) ^ (hash >> 28) ^ Int(scalar.value)) % MODULUS + } + } + return hash +} + +@main +enum SimpleHash { + /* Driver Code */ + static func main() { + let key = "Hello アルゴリズム" + + var hash = addHash(key: key) + print("加算ハッシュ値は \(hash)") + + hash = mulHash(key: key) + print("乗算ハッシュ値は \(hash)") + + hash = xorHash(key: key) + print("XORハッシュ値は \(hash)") + + hash = rotHash(key: key) + print("回転ハッシュ値は \(hash)") + } +} diff --git a/ja/codes/swift/chapter_heap/heap.swift b/ja/codes/swift/chapter_heap/heap.swift new file mode 100644 index 000000000..756480dfd --- /dev/null +++ b/ja/codes/swift/chapter_heap/heap.swift @@ -0,0 +1,62 @@ +/** + * File: heap.swift + * Created Time: 2024-03-17 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import HeapModule +import utils + +func testPush(heap: inout Heap, val: Int) { + heap.insert(val) + print("\n要素 \(val) をヒープに追加した後\n") + PrintUtil.printHeap(queue: heap.unordered) +} + +func testPop(heap: inout Heap) { + let val = heap.removeMax() + print("\nヒープ先頭要素 \(val) を取り出した後\n") + PrintUtil.printHeap(queue: heap.unordered) +} + +@main +enum _Heap { + /* Driver Code */ + static func main() { + /* ヒープを初期化 */ + // Swift の Heap 型は最大ヒープと最小ヒープの両方をサポートする + var heap = Heap() + + /* 要素をヒープに追加 */ + testPush(heap: &heap, val: 1) + testPush(heap: &heap, val: 3) + testPush(heap: &heap, val: 2) + testPush(heap: &heap, val: 5) + testPush(heap: &heap, val: 4) + + /* ヒープ頂点の要素を取得 */ + let peek = heap.max() + print("\nヒープ先頭要素は \(peek!)\n") + + /* ヒープ頂点の要素を取り出す */ + testPop(heap: &heap) + testPop(heap: &heap) + testPop(heap: &heap) + testPop(heap: &heap) + testPop(heap: &heap) + + /* ヒープのサイズを取得 */ + let size = heap.count + print("\nヒープ内の要素数は \(size)\n") + + /* ヒープが空かどうかを判定 */ + let isEmpty = heap.isEmpty + print("\nヒープが空かどうか \(isEmpty)\n") + + /* リストを入力してヒープを構築 */ + // 時間計算量は O(n) であり、O(nlogn) ではない + let heap2 = Heap([1, 3, 2, 5, 4]) + print("\nリストを入力してヒープを構築した後") + PrintUtil.printHeap(queue: heap2.unordered) + } +} diff --git a/ja/codes/swift/chapter_heap/my_heap.swift b/ja/codes/swift/chapter_heap/my_heap.swift new file mode 100644 index 000000000..3a2946767 --- /dev/null +++ b/ja/codes/swift/chapter_heap/my_heap.swift @@ -0,0 +1,163 @@ +/** + * File: my_heap.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 最大ヒープ */ +class MaxHeap { + private var maxHeap: [Int] + + /* コンストラクタ。入力リストに基づいてヒープを構築する */ + init(nums: [Int]) { + // リスト要素をそのままヒープに追加 + maxHeap = nums + // 葉ノード以外のすべてのノードをヒープ化 + for i in (0 ... parent(i: size() - 1)).reversed() { + siftDown(i: i) + } + } + + /* 左子ノードのインデックスを取得 */ + private func left(i: Int) -> Int { + 2 * i + 1 + } + + /* 右子ノードのインデックスを取得 */ + private func right(i: Int) -> Int { + 2 * i + 2 + } + + /* 親ノードのインデックスを取得 */ + private func parent(i: Int) -> Int { + (i - 1) / 2 // 切り捨て除算 + } + + /* 要素を交換 */ + private func swap(i: Int, j: Int) { + maxHeap.swapAt(i, j) + } + + /* ヒープのサイズを取得 */ + func size() -> Int { + maxHeap.count + } + + /* ヒープが空かどうかを判定 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* ヒープ先頭要素にアクセス */ + func peek() -> Int { + maxHeap[0] + } + + /* 要素をヒープに追加 */ + func push(val: Int) { + // ノードを追加 + maxHeap.append(val) + // 下から上へヒープ化 + siftUp(i: size() - 1) + } + + /* ノード i から始めて、下から上へヒープ化 */ + private func siftUp(i: Int) { + var i = i + while true { + // ノード i の親ノードを取得 + let p = parent(i: i) + // 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 + if p < 0 || maxHeap[i] <= maxHeap[p] { + break + } + // 2 つのノードを交換 + swap(i: i, j: p) + // ループで下から上へヒープ化 + i = p + } + } + + /* 要素をヒープから取り出す */ + func pop() -> Int { + // 空判定の処理 + if isEmpty() { + fatalError("ヒープが空です") + } + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + swap(i: 0, j: size() - 1) + // ノードを削除 + let val = maxHeap.remove(at: size() - 1) + // 上から下へヒープ化 + siftDown(i: 0) + // ヒープ先頭要素を返す + return val + } + + /* ノード i から始めて、上から下へヒープ化 */ + private func siftDown(i: Int) { + var i = i + while true { + // ノード i, l, r のうち値が最大のノードを ma とする + let l = left(i: i) + let r = right(i: i) + var ma = i + if l < size(), maxHeap[l] > maxHeap[ma] { + ma = l + } + if r < size(), maxHeap[r] > maxHeap[ma] { + ma = r + } + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + if ma == i { + break + } + // 2 つのノードを交換 + swap(i: i, j: ma) + // ループで上から下へヒープ化 + i = ma + } + } + + /* ヒープ(二分木)を出力 */ + func print() { + let queue = maxHeap + PrintUtil.printHeap(queue: queue) + } +} + +@main +enum MyHeap { + /* Driver Code */ + static func main() { + /* 最大ヒープを初期化 */ + let maxHeap = MaxHeap(nums: [9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) + print("\nリストを入力してヒープを構築した後") + maxHeap.print() + + /* ヒープ頂点の要素を取得 */ + var peek = maxHeap.peek() + print("\nヒープ先頭要素は \(peek)") + + /* 要素をヒープに追加 */ + let val = 7 + maxHeap.push(val: val) + print("\n要素 \(val) をヒープに追加した後") + maxHeap.print() + + /* ヒープ頂点の要素を取り出す */ + peek = maxHeap.pop() + print("\nヒープ先頭要素 \(peek) を取り出した後") + maxHeap.print() + + /* ヒープのサイズを取得 */ + let size = maxHeap.size() + print("\nヒープ内の要素数は \(size)") + + /* ヒープが空かどうかを判定 */ + let isEmpty = maxHeap.isEmpty() + print("\nヒープが空かどうか \(isEmpty)") + } +} diff --git a/ja/codes/swift/chapter_heap/top_k.swift b/ja/codes/swift/chapter_heap/top_k.swift new file mode 100644 index 000000000..10ed5c044 --- /dev/null +++ b/ja/codes/swift/chapter_heap/top_k.swift @@ -0,0 +1,36 @@ +/** + * File: top_k.swift + * Created Time: 2023-07-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import HeapModule +import utils + +/* ヒープに基づいて配列中の最大の k 個の要素を探す */ +func topKHeap(nums: [Int], k: Int) -> [Int] { + // 最小ヒープを初期化し、先頭 k 個の要素でヒープを構築する + var heap = Heap(nums.prefix(k)) + // k+1 番目の要素から開始し、ヒープ長を k に保つ + for i in nums.indices.dropFirst(k) { + // 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する + if nums[i] > heap.min()! { + _ = heap.removeMin() + heap.insert(nums[i]) + } + } + return heap.unordered +} + +@main +enum TopK { + /* Driver Code */ + static func main() { + let nums = [1, 7, 6, 3, 2] + let k = 3 + + let res = topKHeap(nums: nums, k: k) + print("最大の \(k) 個の要素は") + PrintUtil.printHeap(queue: res) + } +} diff --git a/ja/codes/swift/chapter_searching/binary_search.swift b/ja/codes/swift/chapter_searching/binary_search.swift new file mode 100644 index 000000000..a951a5c8d --- /dev/null +++ b/ja/codes/swift/chapter_searching/binary_search.swift @@ -0,0 +1,62 @@ +/** + * File: binary_search.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 二分探索(両閉区間) */ +func binarySearch(nums: [Int], target: Int) -> Int { + // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す + var i = nums.startIndex + var j = nums.endIndex - 1 + // ループし、探索区間が空になったら終了する(i > j で空) + while i <= j { + let m = i + (j - i) / 2 // 中点インデックス m を計算 + if nums[m] < target { // この場合、target は区間 [m+1, j] にある + i = m + 1 + } else if nums[m] > target { // この場合、target は区間 [i, m-1] にある + j = m - 1 + } else { // 目標要素が見つかったらそのインデックスを返す + return m + } + } + // 目標要素が見つからなければ -1 を返す + return -1 +} + +/* 二分探索(左閉右開区間) */ +func binarySearchLCRO(nums: [Int], target: Int) -> Int { + // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す + var i = nums.startIndex + var j = nums.endIndex + // ループし、探索区間が空になったら終了する(i = j で空) + while i < j { + let m = i + (j - i) / 2 // 中点インデックス m を計算 + if nums[m] < target { // この場合、target は区間 [m+1, j) にある + i = m + 1 + } else if nums[m] > target { // この場合、target は区間 [i, m) にある + j = m + } else { // 目標要素が見つかったらそのインデックスを返す + return m + } + } + // 目標要素が見つからなければ -1 を返す + return -1 +} + +@main +enum BinarySearch { + /* Driver Code */ + static func main() { + let target = 6 + let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + /* 二分探索(両閉区間) */ + var index = binarySearch(nums: nums, target: target) + print("対象要素 6 のインデックス = \(index)") + + /* 二分探索(左閉右開区間) */ + index = binarySearchLCRO(nums: nums, target: target) + print("対象要素 6 のインデックス = \(index)") + } +} diff --git a/ja/codes/swift/chapter_searching/binary_search_edge.swift b/ja/codes/swift/chapter_searching/binary_search_edge.swift new file mode 100644 index 000000000..3457a4c45 --- /dev/null +++ b/ja/codes/swift/chapter_searching/binary_search_edge.swift @@ -0,0 +1,51 @@ +/** + * File: binary_search_edge.swift + * Created Time: 2023-08-06 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import binary_search_insertion_target + +/* 最も左の target を二分探索 */ +func binarySearchLeftEdge(nums: [Int], target: Int) -> Int { + // target の挿入位置を探すのと等価 + let i = binarySearchInsertion(nums: nums, target: target) + // target が見つからなければ、-1 を返す + if i == nums.endIndex || nums[i] != target { + return -1 + } + // target が見つかったら、インデックス i を返す + return i +} + +/* 最も右の target を二分探索 */ +func binarySearchRightEdge(nums: [Int], target: Int) -> Int { + // 最左の target + 1 を探す問題に変換する + let i = binarySearchInsertion(nums: nums, target: target + 1) + // j は最も右の target を指し、i は target より大きい最初の要素を指す + let j = i - 1 + // target が見つからなければ、-1 を返す + if j == -1 || nums[j] != target { + return -1 + } + // target が見つかったら、インデックス j を返す + return j +} + +@main +enum BinarySearchEdge { + /* Driver Code */ + static func main() { + // 重複要素を含む配列 + let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print("\n配列 nums = \(nums)") + + // 二分探索で左端と右端を探す + for target in [6, 7] { + var index = binarySearchLeftEdge(nums: nums, target: target) + print("最も左にある要素 \(target) のインデックスは \(index)") + index = binarySearchRightEdge(nums: nums, target: target) + print("最も右にある要素 \(target) のインデックスは \(index)") + } + } +} diff --git a/ja/codes/swift/chapter_searching/binary_search_insertion.swift b/ja/codes/swift/chapter_searching/binary_search_insertion.swift new file mode 100644 index 000000000..a0c7e2574 --- /dev/null +++ b/ja/codes/swift/chapter_searching/binary_search_insertion.swift @@ -0,0 +1,71 @@ +/** + * File: binary_search_insertion.swift + * Created Time: 2023-08-06 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 二分探索で挿入位置を探す(重複要素なし) */ +func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { + // 両閉区間 [0, n-1] を初期化 + var i = nums.startIndex + var j = nums.endIndex - 1 + while i <= j { + let m = i + (j - i) / 2 // 中点インデックス m を計算 + if nums[m] < target { + i = m + 1 // target は区間 [m+1, j] にある + } else if nums[m] > target { + j = m - 1 // target は区間 [i, m-1] にある + } else { + return m // target が見つかったら、挿入位置 m を返す + } + } + // target が見つからなければ、挿入位置 i を返す + return i +} + +/* 二分探索で挿入位置を探す(重複要素あり) */ +public func binarySearchInsertion(nums: [Int], target: Int) -> Int { + // 両閉区間 [0, n-1] を初期化 + var i = nums.startIndex + var j = nums.endIndex - 1 + while i <= j { + let m = i + (j - i) / 2 // 中点インデックス m を計算 + if nums[m] < target { + i = m + 1 // target は区間 [m+1, j] にある + } else if nums[m] > target { + j = m - 1 // target は区間 [i, m-1] にある + } else { + j = m - 1 // target より小さい最初の要素は区間 [i, m-1] にある + } + } + // 挿入位置 i を返す + return i +} + +#if !TARGET + +@main +enum BinarySearchInsertion { + /* Driver Code */ + static func main() { + // 重複要素のない配列 + var nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + print("\n配列 nums = \(nums)") + // 二分探索で挿入位置を探す + for target in [6, 9] { + let index = binarySearchInsertionSimple(nums: nums, target: target) + print("要素 \(target) の挿入位置のインデックスは \(index)") + } + + // 重複要素を含む配列 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print("\n配列 nums = \(nums)") + // 二分探索で挿入位置を探す + for target in [2, 6, 20] { + let index = binarySearchInsertion(nums: nums, target: target) + print("要素 \(target) の挿入位置のインデックスは \(index)") + } + } +} + +#endif diff --git a/ja/codes/swift/chapter_searching/binary_search_insertion_target.swift b/ja/codes/swift/chapter_searching/binary_search_insertion_target.swift new file mode 100644 index 000000000..a0c7e2574 --- /dev/null +++ b/ja/codes/swift/chapter_searching/binary_search_insertion_target.swift @@ -0,0 +1,71 @@ +/** + * File: binary_search_insertion.swift + * Created Time: 2023-08-06 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 二分探索で挿入位置を探す(重複要素なし) */ +func binarySearchInsertionSimple(nums: [Int], target: Int) -> Int { + // 両閉区間 [0, n-1] を初期化 + var i = nums.startIndex + var j = nums.endIndex - 1 + while i <= j { + let m = i + (j - i) / 2 // 中点インデックス m を計算 + if nums[m] < target { + i = m + 1 // target は区間 [m+1, j] にある + } else if nums[m] > target { + j = m - 1 // target は区間 [i, m-1] にある + } else { + return m // target が見つかったら、挿入位置 m を返す + } + } + // target が見つからなければ、挿入位置 i を返す + return i +} + +/* 二分探索で挿入位置を探す(重複要素あり) */ +public func binarySearchInsertion(nums: [Int], target: Int) -> Int { + // 両閉区間 [0, n-1] を初期化 + var i = nums.startIndex + var j = nums.endIndex - 1 + while i <= j { + let m = i + (j - i) / 2 // 中点インデックス m を計算 + if nums[m] < target { + i = m + 1 // target は区間 [m+1, j] にある + } else if nums[m] > target { + j = m - 1 // target は区間 [i, m-1] にある + } else { + j = m - 1 // target より小さい最初の要素は区間 [i, m-1] にある + } + } + // 挿入位置 i を返す + return i +} + +#if !TARGET + +@main +enum BinarySearchInsertion { + /* Driver Code */ + static func main() { + // 重複要素のない配列 + var nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + print("\n配列 nums = \(nums)") + // 二分探索で挿入位置を探す + for target in [6, 9] { + let index = binarySearchInsertionSimple(nums: nums, target: target) + print("要素 \(target) の挿入位置のインデックスは \(index)") + } + + // 重複要素を含む配列 + nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] + print("\n配列 nums = \(nums)") + // 二分探索で挿入位置を探す + for target in [2, 6, 20] { + let index = binarySearchInsertion(nums: nums, target: target) + print("要素 \(target) の挿入位置のインデックスは \(index)") + } + } +} + +#endif diff --git a/ja/codes/swift/chapter_searching/hashing_search.swift b/ja/codes/swift/chapter_searching/hashing_search.swift new file mode 100644 index 000000000..a7bf35d10 --- /dev/null +++ b/ja/codes/swift/chapter_searching/hashing_search.swift @@ -0,0 +1,50 @@ +/** + * File: hashing_search.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* ハッシュ探索(配列) */ +func hashingSearchArray(map: [Int: Int], target: Int) -> Int { + // ハッシュテーブルの key: 目標要素、value: インデックス + // ハッシュテーブルにこの key がなければ -1 を返す + return map[target, default: -1] +} + +/* ハッシュ探索(連結リスト) */ +func hashingSearchLinkedList(map: [Int: ListNode], target: Int) -> ListNode? { + // ハッシュテーブルの key: 目標ノード値、value: ノードオブジェクト + // ハッシュテーブルにこの key がなければ null を返す + return map[target] +} + +@main +enum HashingSearch { + /* Driver Code */ + static func main() { + let target = 3 + + /* ハッシュ探索(配列) */ + let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + // ハッシュテーブルを初期化 + var map: [Int: Int] = [:] + for i in nums.indices { + map[nums[i]] = i // key: 要素、value: インデックス + } + let index = hashingSearchArray(map: map, target: target) + print("目標要素 3 のインデックス = \(index)") + + /* ハッシュ探索(連結リスト) */ + var head = ListNode.arrToLinkedList(arr: nums) + // ハッシュテーブルを初期化 + var map1: [Int: ListNode] = [:] + while head != nil { + map1[head!.val] = head! // key: ノード値、value: ノード + head = head?.next + } + let node = hashingSearchLinkedList(map: map1, target: target) + print("目標ノード値 3 に対応するノードオブジェクトは \(node!)") + } +} diff --git a/ja/codes/swift/chapter_searching/linear_search.swift b/ja/codes/swift/chapter_searching/linear_search.swift new file mode 100644 index 000000000..0296a46a6 --- /dev/null +++ b/ja/codes/swift/chapter_searching/linear_search.swift @@ -0,0 +1,53 @@ +/** + * File: linear_search.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 線形探索(配列) */ +func linearSearchArray(nums: [Int], target: Int) -> Int { + // 配列を走査 + for i in nums.indices { + // 目標要素が見つかったらそのインデックスを返す + if nums[i] == target { + return i + } + } + // 目標要素が見つからなければ -1 を返す + return -1 +} + +/* 線形探索(連結リスト) */ +func linearSearchLinkedList(head: ListNode?, target: Int) -> ListNode? { + var head = head + // 連結リストを走査 + while head != nil { + // 対象ノードが見つかったら、それを返す + if head?.val == target { + return head + } + head = head?.next + } + // 対象ノードが見つからない場合は null を返す + return nil +} + +@main +enum LinearSearch { + /* Driver Code */ + static func main() { + let target = 3 + + /* 配列で線形探索を行う */ + let nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + let index = linearSearchArray(nums: nums, target: target) + print("目標要素 3 のインデックス = \(index)") + + /* 連結リストで線形探索を行う */ + let head = ListNode.arrToLinkedList(arr: nums) + let node = linearSearchLinkedList(head: head, target: target) + print("目標ノード値 3 に対応するノードオブジェクトは \(node!)") + } +} diff --git a/ja/codes/swift/chapter_searching/two_sum.swift b/ja/codes/swift/chapter_searching/two_sum.swift new file mode 100644 index 000000000..9660f06c3 --- /dev/null +++ b/ja/codes/swift/chapter_searching/two_sum.swift @@ -0,0 +1,49 @@ +/** + * File: two_sum.swift + * Created Time: 2023-01-03 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 方法 1:総当たり列挙 */ +func twoSumBruteForce(nums: [Int], target: Int) -> [Int] { + // 2重ループのため、時間計算量は O(n^2) + for i in nums.indices.dropLast() { + for j in nums.indices.dropFirst(i + 1) { + if nums[i] + nums[j] == target { + return [i, j] + } + } + } + return [0] +} + +/* 方法 2:補助ハッシュテーブル */ +func twoSumHashTable(nums: [Int], target: Int) -> [Int] { + // 補助ハッシュテーブルを使用し、空間計算量は O(n) + var dic: [Int: Int] = [:] + // 単一ループで、時間計算量は O(n) + for i in nums.indices { + if let j = dic[target - nums[i]] { + return [j, i] + } + dic[nums[i]] = i + } + return [0] +} + +@main +enum LeetcodeTwoSum { + /* Driver Code */ + static func main() { + // ======= Test Case ======= + let nums = [2, 7, 11, 15] + let target = 13 + // ====== Driver Code ====== + // 方法 1 + var res = twoSumBruteForce(nums: nums, target: target) + print("方法1 res = \(res)") + // 方法 2 + res = twoSumHashTable(nums: nums, target: target) + print("方法2 res = \(res)") + } +} diff --git a/ja/codes/swift/chapter_sorting/bubble_sort.swift b/ja/codes/swift/chapter_sorting/bubble_sort.swift new file mode 100644 index 000000000..5c8110a93 --- /dev/null +++ b/ja/codes/swift/chapter_sorting/bubble_sort.swift @@ -0,0 +1,51 @@ +/** + * File: bubble_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* バブルソート */ +func bubbleSort(nums: inout [Int]) { + // 外側のループ:未ソート区間は [0, i] + for i in nums.indices.dropFirst().reversed() { + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for j in 0 ..< i { + if nums[j] > nums[j + 1] { + // nums[j] と nums[j + 1] を交換 + nums.swapAt(j, j + 1) + } + } + } +} + +/* バブルソート(フラグ最適化) */ +func bubbleSortWithFlag(nums: inout [Int]) { + // 外側のループ:未ソート区間は [0, i] + for i in nums.indices.dropFirst().reversed() { + var flag = false // フラグを初期化する + for j in 0 ..< i { + if nums[j] > nums[j + 1] { + // nums[j] と nums[j + 1] を交換 + nums.swapAt(j, j + 1) + flag = true // 交換する要素を記録 + } + } + if !flag { // このバブル処理で要素交換が一度もなければそのまま終了 + break + } + } +} + +@main +enum BubbleSort { + /* Driver Code */ + static func main() { + var nums = [4, 1, 3, 1, 5, 2] + bubbleSort(nums: &nums) + print("バブルソート完了後 nums = \(nums)") + + var nums1 = [4, 1, 3, 1, 5, 2] + bubbleSortWithFlag(nums: &nums1) + print("バブルソート完了後 nums1 = \(nums1)") + } +} diff --git a/ja/codes/swift/chapter_sorting/bucket_sort.swift b/ja/codes/swift/chapter_sorting/bucket_sort.swift new file mode 100644 index 000000000..b09cf7990 --- /dev/null +++ b/ja/codes/swift/chapter_sorting/bucket_sort.swift @@ -0,0 +1,43 @@ +/** + * File: bucket_sort.swift + * Created Time: 2023-03-27 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* バケットソート */ +func bucketSort(nums: inout [Double]) { + // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする + let k = nums.count / 2 + var buckets = (0 ..< k).map { _ in [Double]() } + // 1. 配列要素を各バケットに振り分ける + for num in nums { + // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する + let i = Int(num * Double(k)) + // num をバケット i に追加 + buckets[i].append(num) + } + // 2. 各バケットをソートする + for i in buckets.indices { + // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい + buckets[i].sort() + } + // 3. バケットを走査して結果を結合 + var i = nums.startIndex + for bucket in buckets { + for num in bucket { + nums[i] = num + i += 1 + } + } +} + +@main +enum BucketSort { + /* Driver Code */ + static func main() { + // 入力データは範囲 [0, 1) の浮動小数点数とする + var nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] + bucketSort(nums: &nums) + print("バケットソート完了後 nums = \(nums)") + } +} diff --git a/ja/codes/swift/chapter_sorting/counting_sort.swift b/ja/codes/swift/chapter_sorting/counting_sort.swift new file mode 100644 index 000000000..3f5fb1906 --- /dev/null +++ b/ja/codes/swift/chapter_sorting/counting_sort.swift @@ -0,0 +1,70 @@ +/** + * File: counting_sort.swift + * Created Time: 2023-03-22 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 計数ソート */ +// 簡易実装のため、オブジェクトのソートには使えない +func countingSortNaive(nums: inout [Int]) { + // 1. 配列の最大要素 m を求める + let m = nums.max()! + // 2. 各数値の出現回数を数える + // counter[num] は num の出現回数を表す + var counter = Array(repeating: 0, count: m + 1) + for num in nums { + counter[num] += 1 + } + // 3. counter を走査し、各要素を元の配列 nums に書き戻す + var i = 0 + for num in 0 ..< m + 1 { + for _ in 0 ..< counter[num] { + nums[i] = num + i += 1 + } + } +} + +/* 計数ソート */ +// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである +func countingSort(nums: inout [Int]) { + // 1. 配列の最大要素 m を求める + let m = nums.max()! + // 2. 各数値の出現回数を数える + // counter[num] は num の出現回数を表す + var counter = Array(repeating: 0, count: m + 1) + for num in nums { + counter[num] += 1 + } + // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する + // つまり counter[num]-1 は、num が res に最後に現れるインデックス + for i in 0 ..< m { + counter[i + 1] += counter[i] + } + // 4. nums を逆順に走査し、各要素を結果配列 res に格納する + // 結果を記録するための配列 res を初期化 + var res = Array(repeating: 0, count: nums.count) + for i in nums.indices.reversed() { + let num = nums[i] + res[counter[num] - 1] = num // num を対応するインデックスに配置 + counter[num] -= 1 // 累積和を 1 減らして、次に num を配置するインデックスを得る + } + // 結果配列 res で元の配列 nums を上書きする + for i in nums.indices { + nums[i] = res[i] + } +} + +@main +enum CountingSort { + /* Driver Code */ + static func main() { + var nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + countingSortNaive(nums: &nums) + print("カウントソート(オブジェクトはソート不可)完了後 nums = \(nums)") + + var nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + countingSort(nums: &nums1) + print("カウントソート完了後 nums1 = \(nums1)") + } +} diff --git a/ja/codes/swift/chapter_sorting/heap_sort.swift b/ja/codes/swift/chapter_sorting/heap_sort.swift new file mode 100644 index 000000000..ef486305c --- /dev/null +++ b/ja/codes/swift/chapter_sorting/heap_sort.swift @@ -0,0 +1,55 @@ +/** + * File: heap_sort.swift + * Created Time: 2023-05-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* ヒープの長さは n。ノード i から下方向にヒープ化 */ +func siftDown(nums: inout [Int], n: Int, i: Int) { + var i = i + while true { + // ノード i, l, r のうち値が最大のノードを ma とする + let l = 2 * i + 1 + let r = 2 * i + 2 + var ma = i + if l < n, nums[l] > nums[ma] { + ma = l + } + if r < n, nums[r] > nums[ma] { + ma = r + } + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + if ma == i { + break + } + // 2 つのノードを交換 + nums.swapAt(i, ma) + // ループで上から下へヒープ化 + i = ma + } +} + +/* ヒープソート */ +func heapSort(nums: inout [Int]) { + // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する + for i in stride(from: nums.count / 2 - 1, through: 0, by: -1) { + siftDown(nums: &nums, n: nums.count, i: i) + } + // ヒープから最大要素を取り出し、n-1 回繰り返す + for i in nums.indices.dropFirst().reversed() { + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + nums.swapAt(0, i) + // 根ノードを起点に、上から下へヒープ化 + siftDown(nums: &nums, n: i, i: 0) + } +} + +@main +enum HeapSort { + /* Driver Code */ + static func main() { + var nums = [4, 1, 3, 1, 5, 2] + heapSort(nums: &nums) + print("ヒープソート完了後 nums = \(nums)") + } +} diff --git a/ja/codes/swift/chapter_sorting/insertion_sort.swift b/ja/codes/swift/chapter_sorting/insertion_sort.swift new file mode 100644 index 000000000..28801add2 --- /dev/null +++ b/ja/codes/swift/chapter_sorting/insertion_sort.swift @@ -0,0 +1,30 @@ +/** + * File: insertion_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 挿入ソート */ +func insertionSort(nums: inout [Int]) { + // 外側ループ:整列済み区間は [0, i-1] + for i in nums.indices.dropFirst() { + let base = nums[i] + var j = i - 1 + // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する + while j >= 0, nums[j] > base { + nums[j + 1] = nums[j] // nums[j] を 1 つ右へ移動する + j -= 1 + } + nums[j + 1] = base // base を正しい位置に配置する + } +} + +@main +enum InsertionSort { + /* Driver Code */ + static func main() { + var nums = [4, 1, 3, 1, 5, 2] + insertionSort(nums: &nums) + print("挿入ソート完了後 nums = \(nums)") + } +} diff --git a/ja/codes/swift/chapter_sorting/merge_sort.swift b/ja/codes/swift/chapter_sorting/merge_sort.swift new file mode 100644 index 000000000..5663db4df --- /dev/null +++ b/ja/codes/swift/chapter_sorting/merge_sort.swift @@ -0,0 +1,65 @@ +/** + * File: merge_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 左部分配列と右部分配列をマージ */ +func merge(nums: inout [Int], left: Int, mid: Int, right: Int) { + // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] + // マージ結果を格納する一時配列 tmp を作成 + var tmp = Array(repeating: 0, count: right - left + 1) + // 左右の部分配列の開始インデックスを初期化する + var i = left, j = mid + 1, k = 0 + // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする + while i <= mid, j <= right { + if nums[i] <= nums[j] { + tmp[k] = nums[i] + i += 1 + } else { + tmp[k] = nums[j] + j += 1 + } + k += 1 + } + // 左右の部分配列の残り要素を一時配列にコピーする + while i <= mid { + tmp[k] = nums[i] + i += 1 + k += 1 + } + while j <= right { + tmp[k] = nums[j] + j += 1 + k += 1 + } + // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする + for k in tmp.indices { + nums[left + k] = tmp[k] + } +} + +/* マージソート */ +func mergeSort(nums: inout [Int], left: Int, right: Int) { + // 終了条件 + if left >= right { // 部分配列の長さが 1 になったら再帰を終了 + return + } + // 分割フェーズ + let mid = left + (right - left) / 2 // 中点を計算 + mergeSort(nums: &nums, left: left, right: mid) // 左部分配列を再帰処理 + mergeSort(nums: &nums, left: mid + 1, right: right) // 右部分配列を再帰処理 + // マージフェーズ + merge(nums: &nums, left: left, mid: mid, right: right) +} + +@main +enum MergeSort { + /* Driver Code */ + static func main() { + /* マージソート */ + var nums = [7, 3, 2, 6, 0, 1, 5, 4] + mergeSort(nums: &nums, left: nums.startIndex, right: nums.endIndex - 1) + print("マージソート完了後 nums = \(nums)") + } +} diff --git a/ja/codes/swift/chapter_sorting/quick_sort.swift b/ja/codes/swift/chapter_sorting/quick_sort.swift new file mode 100644 index 000000000..0c078d699 --- /dev/null +++ b/ja/codes/swift/chapter_sorting/quick_sort.swift @@ -0,0 +1,114 @@ +/** + * File: quick_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* クイックソートクラス */ +/* 番兵分割 */ +func partition(nums: inout [Int], left: Int, right: Int) -> Int { + // nums[left] を基準値とする + var i = left + var j = right + while i < j { + while i < j, nums[j] >= nums[left] { + j -= 1 // 右から左へ基準値未満の最初の要素を探す + } + while i < j, nums[i] <= nums[left] { + i += 1 // 左から右へ基準値より大きい最初の要素を探す + } + nums.swapAt(i, j) // この 2 つの要素を交換 + } + nums.swapAt(i, left) // 基準値を 2 つの部分配列の境界へ交換する + return i // 基準値のインデックスを返す +} + +/* クイックソート */ +func quickSort(nums: inout [Int], left: Int, right: Int) { + // 部分配列の長さが 1 なら再帰を終了する + if left >= right { + return + } + // 番兵分割 + let pivot = partition(nums: &nums, left: left, right: right) + // 左右の部分配列を再帰処理 + quickSort(nums: &nums, left: left, right: pivot - 1) + quickSort(nums: &nums, left: pivot + 1, right: right) +} + +/* クイックソートクラス(中央値ピボット最適化) */ +/* 3つの候補要素の中央値を選ぶ */ +func medianThree(nums: [Int], left: Int, mid: Int, right: Int) -> Int { + let l = nums[left] + let m = nums[mid] + let r = nums[right] + if (l <= m && m <= r) || (r <= m && m <= l) { + return mid // m は l と r の間 + } + if (m <= l && l <= r) || (r <= l && l <= m) { + return left // l は m と r の間 + } + return right +} + +/* 番兵による分割処理(3 点中央値) */ +func partitionMedian(nums: inout [Int], left: Int, right: Int) -> Int { + // 3つの候補要素の中央値を選ぶ + let med = medianThree(nums: nums, left: left, mid: left + (right - left) / 2, right: right) + // 中央値を配列の最左端に交換する + nums.swapAt(left, med) + return partition(nums: &nums, left: left, right: right) +} + +/* クイックソート(中央値の基準値で最適化) */ +func quickSortMedian(nums: inout [Int], left: Int, right: Int) { + // 部分配列の長さが 1 なら再帰を終了する + if left >= right { + return + } + // 番兵分割 + let pivot = partitionMedian(nums: &nums, left: left, right: right) + // 左右の部分配列を再帰処理 + quickSortMedian(nums: &nums, left: left, right: pivot - 1) + quickSortMedian(nums: &nums, left: pivot + 1, right: right) +} + +/* クイックソート(再帰深度最適化) */ +func quickSortTailCall(nums: inout [Int], left: Int, right: Int) { + var left = left + var right = right + // 部分配列の長さが 1 なら終了 + while left < right { + // 番兵による分割処理 + let pivot = partition(nums: &nums, left: left, right: right) + // 2 つの部分配列のうち短いほうにクイックソートを適用する + if (pivot - left) < (right - pivot) { + quickSortTailCall(nums: &nums, left: left, right: pivot - 1) // 左部分配列を再帰的にソート + left = pivot + 1 // 未ソート区間の残りは [pivot + 1, right] + } else { + quickSortTailCall(nums: &nums, left: pivot + 1, right: right) // 右部分配列を再帰的にソート + right = pivot - 1 // 未ソート区間の残りは [left, pivot - 1] + } + } +} + +@main +enum QuickSort { + /* Driver Code */ + static func main() { + /* クイックソート */ + var nums = [2, 4, 1, 0, 3, 5] + quickSort(nums: &nums, left: nums.startIndex, right: nums.endIndex - 1) + print("クイックソート完了後 nums = \(nums)") + + /* クイックソート(中央値の基準値で最適化) */ + var nums1 = [2, 4, 1, 0, 3, 5] + quickSortMedian(nums: &nums1, left: nums1.startIndex, right: nums1.endIndex - 1) + print("クイックソート(中央値ピボット最適化)完了後 nums1 = \(nums1)") + + /* クイックソート(再帰深度最適化) */ + var nums2 = [2, 4, 1, 0, 3, 5] + quickSortTailCall(nums: &nums2, left: nums2.startIndex, right: nums2.endIndex - 1) + print("クイックソート(再帰深度最適化)完了後 nums2 = \(nums2)") + } +} diff --git a/ja/codes/swift/chapter_sorting/radix_sort.swift b/ja/codes/swift/chapter_sorting/radix_sort.swift new file mode 100644 index 000000000..04c9538e6 --- /dev/null +++ b/ja/codes/swift/chapter_sorting/radix_sort.swift @@ -0,0 +1,79 @@ +/** + * File: radix_sort.swift + * Created Time: 2023-01-29 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */ +func digit(num: Int, exp: Int) -> Int { + // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す + (num / exp) % 10 +} + +/* 計数ソート(nums の k 桁目でソート) */ +func countingSortDigit(nums: inout [Int], exp: Int) { + // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 + var counter = Array(repeating: 0, count: 10) + // 0~9 の各数字の出現回数を集計する + for i in nums.indices { + let d = digit(num: nums[i], exp: exp) // nums[i] の第 k 位を取得し、d とする + counter[d] += 1 // 数字 d の出現回数を数える + } + // 累積和を求め、「出現回数」を「配列インデックス」に変換する + for i in 1 ..< 10 { + counter[i] += counter[i - 1] + } + // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する + var res = Array(repeating: 0, count: nums.count) + for i in nums.indices.reversed() { + let d = digit(num: nums[i], exp: exp) + let j = counter[d] - 1 // d の配列内インデックス j を取得する + res[j] = nums[i] // 現在の要素をインデックス j に格納する + counter[d] -= 1 // d の個数を 1 減らす + } + // 結果で元の配列 nums を上書きする + for i in nums.indices { + nums[i] = res[i] + } +} + +/* 基数ソート */ +func radixSort(nums: inout [Int]) { + // 最大桁数の判定用に配列の最大要素を取得 + var m = Int.min + for num in nums { + if num > m { + m = num + } + } + // 下位桁から上位桁の順に走査する + for exp in sequence(first: 1, next: { m >= ($0 * 10) ? $0 * 10 : nil }) { + // 配列要素の k 桁目に対して計数ソートを行う + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // つまり exp = 10^(k-1) + countingSortDigit(nums: &nums, exp: exp) + } +} + +@main +enum RadixSort { + /* Driver Code */ + static func main() { + // 基数ソート + var nums = [ + 10_546_151, + 35_663_510, + 42_865_989, + 34_862_445, + 81_883_077, + 88_906_420, + 72_429_244, + 30_524_779, + 82_060_337, + 63_832_996, + ] + radixSort(nums: &nums) + print("基数ソート完了後 nums = \(nums)") + } +} diff --git a/ja/codes/swift/chapter_sorting/selection_sort.swift b/ja/codes/swift/chapter_sorting/selection_sort.swift new file mode 100644 index 000000000..6f7a9ccc5 --- /dev/null +++ b/ja/codes/swift/chapter_sorting/selection_sort.swift @@ -0,0 +1,31 @@ +/** + * File: selection_sort.swift + * Created Time: 2023-05-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 選択ソート */ +func selectionSort(nums: inout [Int]) { + // 外側ループ:未整列区間は [i, n-1] + for i in nums.indices.dropLast() { + // 内側のループ:未ソート区間の最小要素を見つける + var k = i + for j in nums.indices.dropFirst(i + 1) { + if nums[j] < nums[k] { + k = j // 最小要素のインデックスを記録 + } + } + // その最小要素を未整列区間の先頭要素と交換する + nums.swapAt(i, k) + } +} + +@main +enum SelectionSort { + /* Driver Code */ + static func main() { + var nums = [4, 1, 3, 1, 5, 2] + selectionSort(nums: &nums) + print("選択ソート完了後 nums = \(nums)") + } +} diff --git a/ja/codes/swift/chapter_stack_and_queue/array_deque.swift b/ja/codes/swift/chapter_stack_and_queue/array_deque.swift new file mode 100644 index 000000000..6d2c27075 --- /dev/null +++ b/ja/codes/swift/chapter_stack_and_queue/array_deque.swift @@ -0,0 +1,148 @@ +/** + * File: array_deque.swift + * Created Time: 2023-02-22 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 循環配列ベースの両端キュー */ +class ArrayDeque { + private var nums: [Int] // 両端キューの要素を格納する配列 + private var front: Int // 先頭ポインタ。先頭要素を指す + private var _size: Int // 両端キューの長さ + + /* コンストラクタ */ + init(capacity: Int) { + nums = Array(repeating: 0, count: capacity) + front = 0 + _size = 0 + } + + /* 両端キューの容量を取得 */ + func capacity() -> Int { + nums.count + } + + /* 両端キューの長さを取得 */ + func size() -> Int { + _size + } + + /* 両端キューが空かどうかを判定 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* 循環配列のインデックスを計算 */ + private func index(i: Int) -> Int { + // 剰余演算により配列の先頭と末尾をつなげる + // i が配列の末尾を越えたら先頭に戻る + // i が配列の先頭を越えて前に出たら末尾に戻る + (i + capacity()) % capacity() + } + + /* キュー先頭にエンキュー */ + func pushFirst(num: Int) { + if size() == capacity() { + print("両端キューがいっぱいです") + return + } + // 先頭ポインタを左に 1 つ移動する + // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする + front = index(i: front - 1) + // num をキュー先頭に追加 + nums[front] = num + _size += 1 + } + + /* キュー末尾にエンキュー */ + func pushLast(num: Int) { + if size() == capacity() { + print("両端キューがいっぱいです") + return + } + // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す + let rear = index(i: front + size()) + // num をキュー末尾に追加 + nums[rear] = num + _size += 1 + } + + /* キュー先頭からデキュー */ + func popFirst() -> Int { + let num = peekFirst() + // 先頭ポインタを 1 つ後ろへ進める + front = index(i: front + 1) + _size -= 1 + return num + } + + /* キュー末尾からデキュー */ + func popLast() -> Int { + let num = peekLast() + _size -= 1 + return num + } + + /* キュー先頭の要素にアクセス */ + func peekFirst() -> Int { + if isEmpty() { + fatalError("両端キューが空です") + } + return nums[front] + } + + /* キュー末尾の要素にアクセス */ + func peekLast() -> Int { + if isEmpty() { + fatalError("両端キューが空です") + } + // 末尾要素のインデックスを計算 + let last = index(i: front + size() - 1) + return nums[last] + } + + /* 出力用の配列を返す */ + func toArray() -> [Int] { + // 有効長の範囲内のリスト要素のみを変換 + (front ..< front + size()).map { nums[index(i: $0)] } + } +} + +@main +enum _ArrayDeque { + /* Driver Code */ + static func main() { + /* 両端キューを初期化 */ + let deque = ArrayDeque(capacity: 10) + deque.pushLast(num: 3) + deque.pushLast(num: 2) + deque.pushLast(num: 5) + print("両端キュー deque = \(deque.toArray())") + + /* 要素にアクセス */ + let peekFirst = deque.peekFirst() + print("先頭要素 peekFirst = \(peekFirst)") + let peekLast = deque.peekLast() + print("末尾要素 peekLast = \(peekLast)") + + /* 要素をエンキュー */ + deque.pushLast(num: 4) + print("要素 4 を末尾に追加した後 deque = \(deque.toArray())") + deque.pushFirst(num: 1) + print("要素 1 を先頭に追加した後 deque = \(deque.toArray())") + + /* 要素をデキュー */ + let popLast = deque.popLast() + print("末尾から取り出した要素 = \(popLast),末尾から取り出した後 deque = \(deque.toArray())") + let popFirst = deque.popFirst() + print("先頭から取り出した要素 = \(popFirst),先頭から取り出した後 deque = \(deque.toArray())") + + /* 両端キューの長さを取得 */ + let size = deque.size() + print("両端キューのサイズ size = \(size)") + + /* 両端キューが空かどうかを判定 */ + let isEmpty = deque.isEmpty() + print("両端キューが空かどうか = \(isEmpty)") + } +} diff --git a/ja/codes/swift/chapter_stack_and_queue/array_queue.swift b/ja/codes/swift/chapter_stack_and_queue/array_queue.swift new file mode 100644 index 000000000..56da754ac --- /dev/null +++ b/ja/codes/swift/chapter_stack_and_queue/array_queue.swift @@ -0,0 +1,113 @@ +/** + * File: array_queue.swift + * Created Time: 2023-01-11 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 循環配列ベースのキュー */ +class ArrayQueue { + private var nums: [Int] // キュー要素を格納する配列 + private var front: Int // 先頭ポインタ。先頭要素を指す + private var _size: Int // キューの長さ + + init(capacity: Int) { + // 配列を初期化 + nums = Array(repeating: 0, count: capacity) + front = 0 + _size = 0 + } + + /* キューの容量を取得 */ + func capacity() -> Int { + nums.count + } + + /* キューの長さを取得 */ + func size() -> Int { + _size + } + + /* キューが空かどうかを判定 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* エンキュー */ + func push(num: Int) { + if size() == capacity() { + print("キューがいっぱいです") + return + } + // 末尾ポインタを計算し、末尾インデックス + 1 を指す + // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする + let rear = (front + size()) % capacity() + // num をキュー末尾に追加 + nums[rear] = num + _size += 1 + } + + /* デキュー */ + @discardableResult + func pop() -> Int { + let num = peek() + // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す + front = (front + 1) % capacity() + _size -= 1 + return num + } + + /* キュー先頭の要素にアクセス */ + func peek() -> Int { + if isEmpty() { + fatalError("キューが空です") + } + return nums[front] + } + + /* 配列を返す */ + func toArray() -> [Int] { + // 有効長の範囲内のリスト要素のみを変換 + (front ..< front + size()).map { nums[$0 % capacity()] } + } +} + +@main +enum _ArrayQueue { + /* Driver Code */ + static func main() { + /* キューを初期化 */ + let capacity = 10 + let queue = ArrayQueue(capacity: capacity) + + /* 要素をエンキュー */ + queue.push(num: 1) + queue.push(num: 3) + queue.push(num: 2) + queue.push(num: 5) + queue.push(num: 4) + print("キュー queue = \(queue.toArray())") + + /* キュー先頭の要素にアクセス */ + let peek = queue.peek() + print("先頭要素 peek = \(peek)") + + /* 要素をデキュー */ + let pop = queue.pop() + print("取り出した要素 pop = \(pop),取り出し後 queue = \(queue.toArray())") + + /* キューの長さを取得 */ + let size = queue.size() + print("キューのサイズ size = \(size)") + + /* キューが空かどうかを判定 */ + let isEmpty = queue.isEmpty() + print("キューが空かどうか = \(isEmpty)") + + /* 循環配列をテストする */ + for i in 0 ..< 10 { + queue.push(num: i) + queue.pop() + print("第 \(i) 回のエンキュー + デキュー後 queue = \(queue.toArray())") + } + } +} diff --git a/ja/codes/swift/chapter_stack_and_queue/array_stack.swift b/ja/codes/swift/chapter_stack_and_queue/array_stack.swift new file mode 100644 index 000000000..07fd0a213 --- /dev/null +++ b/ja/codes/swift/chapter_stack_and_queue/array_stack.swift @@ -0,0 +1,85 @@ +/** + * File: array_stack.swift + * Created Time: 2023-01-09 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 配列ベースのスタック */ +class ArrayStack { + private var stack: [Int] + + init() { + // リスト(動的配列)を初期化する + stack = [] + } + + /* スタックの長さを取得 */ + func size() -> Int { + stack.count + } + + /* スタックが空かどうかを判定 */ + func isEmpty() -> Bool { + stack.isEmpty + } + + /* プッシュ */ + func push(num: Int) { + stack.append(num) + } + + /* ポップ */ + @discardableResult + func pop() -> Int { + if isEmpty() { + fatalError("スタックが空です") + } + return stack.removeLast() + } + + /* スタックトップの要素にアクセス */ + func peek() -> Int { + if isEmpty() { + fatalError("スタックが空です") + } + return stack.last! + } + + /* List を Array に変換して返す */ + func toArray() -> [Int] { + stack + } +} + +@main +enum _ArrayStack { + /* Driver Code */ + static func main() { + /* スタックを初期化 */ + let stack = ArrayStack() + + /* 要素をプッシュ */ + stack.push(num: 1) + stack.push(num: 3) + stack.push(num: 2) + stack.push(num: 5) + stack.push(num: 4) + print("スタック stack = \(stack.toArray())") + + /* スタックトップの要素にアクセス */ + let peek = stack.peek() + print("スタックトップ要素 peek = \(peek)") + + /* 要素をポップ */ + let pop = stack.pop() + print("ポップした要素 pop = \(pop)、ポップ後の stack = \(stack.toArray())") + + /* スタックの長さを取得 */ + let size = stack.size() + print("スタックの長さ size = \(size)") + + /* 空かどうかを判定 */ + let isEmpty = stack.isEmpty() + print("スタックが空かどうか = \(isEmpty)") + } +} diff --git a/ja/codes/swift/chapter_stack_and_queue/deque.swift b/ja/codes/swift/chapter_stack_and_queue/deque.swift new file mode 100644 index 000000000..d5327288f --- /dev/null +++ b/ja/codes/swift/chapter_stack_and_queue/deque.swift @@ -0,0 +1,44 @@ +/** + * File: deque.swift + * Created Time: 2023-01-14 + * Author: nuomi1 (nuomi1@qq.com) + */ + +@main +enum Deque { + /* Driver Code */ + static func main() { + /* 両端キューを初期化 */ + // Swift には組み込みの両端キュークラスがないため、Array を両端キューとして使う + var deque: [Int] = [] + + /* 要素をエンキュー */ + deque.append(2) + deque.append(5) + deque.append(4) + deque.insert(3, at: 0) + deque.insert(1, at: 0) + print("両端キュー deque = \(deque)") + + /* 要素にアクセス */ + let peekFirst = deque.first! + print("先頭要素 peekFirst = \(peekFirst)") + let peekLast = deque.last! + print("末尾要素 peekLast = \(peekLast)") + + /* 要素をデキュー */ + // Array を用いる場合、popFirst の計算量は O(n) + let popFirst = deque.removeFirst() + print("先頭からデキューした要素 popFirst = \(popFirst)、先頭からデキュー後の deque = \(deque)") + let popLast = deque.removeLast() + print("末尾からデキューした要素 popLast = \(popLast)、末尾からデキュー後の deque = \(deque)") + + /* 両端キューの長さを取得 */ + let size = deque.count + print("両端キューのサイズ size = \(size)") + + /* 両端キューが空かどうかを判定 */ + let isEmpty = deque.isEmpty + print("両端キューが空かどうか = \(isEmpty)") + } +} diff --git a/ja/codes/swift/chapter_stack_and_queue/linkedlist_deque.swift b/ja/codes/swift/chapter_stack_and_queue/linkedlist_deque.swift new file mode 100644 index 000000000..ad5e46389 --- /dev/null +++ b/ja/codes/swift/chapter_stack_and_queue/linkedlist_deque.swift @@ -0,0 +1,180 @@ +/** + * File: linkedlist_deque.swift + * Created Time: 2023-02-22 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 双方向連結リストノード */ +class ListNode { + var val: Int // ノード値 + var next: ListNode? // 後続ノードへの参照 + weak var prev: ListNode? // 前駆ノードへの参照 + + init(val: Int) { + self.val = val + } +} + +/* 双方向連結リストベースの両端キュー */ +class LinkedListDeque { + private var front: ListNode? // 先頭ノード front + private var rear: ListNode? // 末尾ノード rear + private var _size: Int // 両端キューの長さ + + init() { + _size = 0 + } + + /* 両端キューの長さを取得 */ + func size() -> Int { + _size + } + + /* 両端キューが空かどうかを判定 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* エンキュー操作 */ + private func push(num: Int, isFront: Bool) { + let node = ListNode(val: num) + // 連結リストが空なら、front と rear の両方を node に向ける + if isEmpty() { + front = node + rear = node + } + // 先頭へのエンキュー操作 + else if isFront { + // node を連結リストの先頭に追加 + front?.prev = node + node.next = front + front = node // 先頭ノードを更新する + } + // 末尾へのエンキュー操作 + else { + // node を連結リストの末尾に追加 + rear?.next = node + node.prev = rear + rear = node // 末尾ノードを更新する + } + _size += 1 // キューの長さを更新 + } + + /* キュー先頭にエンキュー */ + func pushFirst(num: Int) { + push(num: num, isFront: true) + } + + /* キュー末尾にエンキュー */ + func pushLast(num: Int) { + push(num: num, isFront: false) + } + + /* デキュー操作 */ + private func pop(isFront: Bool) -> Int { + if isEmpty() { + fatalError("両端キューが空です") + } + let val: Int + // キュー先頭からの取り出し + if isFront { + val = front!.val // 先頭ノードの値を一時保存 + // 先頭ノードを削除 + let fNext = front?.next + if fNext != nil { + fNext?.prev = nil + front?.next = nil + } + front = fNext // 先頭ノードを更新する + } + // キュー末尾からの取り出し + else { + val = rear!.val // 末尾ノードの値を一時保存 + // 末尾ノードを削除 + let rPrev = rear?.prev + if rPrev != nil { + rPrev?.next = nil + rear?.prev = nil + } + rear = rPrev // 末尾ノードを更新する + } + _size -= 1 // キューの長さを更新 + return val + } + + /* キュー先頭からデキュー */ + func popFirst() -> Int { + pop(isFront: true) + } + + /* キュー末尾からデキュー */ + func popLast() -> Int { + pop(isFront: false) + } + + /* キュー先頭の要素にアクセス */ + func peekFirst() -> Int { + if isEmpty() { + fatalError("両端キューが空です") + } + return front!.val + } + + /* キュー末尾の要素にアクセス */ + func peekLast() -> Int { + if isEmpty() { + fatalError("両端キューが空です") + } + return rear!.val + } + + /* 出力用の配列を返す */ + func toArray() -> [Int] { + var node = front + var res = Array(repeating: 0, count: size()) + for i in res.indices { + res[i] = node!.val + node = node?.next + } + return res + } +} + +@main +enum _LinkedListDeque { + /* Driver Code */ + static func main() { + /* 両端キューを初期化 */ + let deque = LinkedListDeque() + deque.pushLast(num: 3) + deque.pushLast(num: 2) + deque.pushLast(num: 5) + print("両端キュー deque = \(deque.toArray())") + + /* 要素にアクセス */ + let peekFirst = deque.peekFirst() + print("先頭要素 peekFirst = \(peekFirst)") + let peekLast = deque.peekLast() + print("末尾要素 peekLast = \(peekLast)") + + /* 要素をエンキュー */ + deque.pushLast(num: 4) + print("要素 4 を末尾に追加した後 deque = \(deque.toArray())") + deque.pushFirst(num: 1) + print("要素 1 を先頭に追加した後 deque = \(deque.toArray())") + + /* 要素をデキュー */ + let popLast = deque.popLast() + print("末尾から取り出した要素 = \(popLast),末尾から取り出した後 deque = \(deque.toArray())") + let popFirst = deque.popFirst() + print("先頭から取り出した要素 = \(popFirst),先頭から取り出した後 deque = \(deque.toArray())") + + /* 両端キューの長さを取得 */ + let size = deque.size() + print("両端キューのサイズ size = \(size)") + + /* 両端キューが空かどうかを判定 */ + let isEmpty = deque.isEmpty() + print("両端キューが空かどうか = \(isEmpty)") + } +} diff --git a/ja/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift b/ja/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift new file mode 100644 index 000000000..53e7dd41f --- /dev/null +++ b/ja/codes/swift/chapter_stack_and_queue/linkedlist_queue.swift @@ -0,0 +1,107 @@ +/** + * File: linkedlist_queue.swift + * Created Time: 2023-01-11 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 連結リストベースのキュー */ +class LinkedListQueue { + private var front: ListNode? // 先頭ノード + private var rear: ListNode? // 末尾ノード + private var _size: Int + + init() { + _size = 0 + } + + /* キューの長さを取得 */ + func size() -> Int { + _size + } + + /* キューが空かどうかを判定 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* エンキュー */ + func push(num: Int) { + // 末尾ノードの後ろに num を追加 + let node = ListNode(x: num) + // キューが空なら、先頭・末尾ノードをともにそのノードに設定 + if front == nil { + front = node + rear = node + } + // キューが空でなければ、そのノードを末尾ノードの後ろに追加 + else { + rear?.next = node + rear = node + } + _size += 1 + } + + /* デキュー */ + @discardableResult + func pop() -> Int { + let num = peek() + // 先頭ノードを削除 + front = front?.next + _size -= 1 + return num + } + + /* キュー先頭の要素にアクセス */ + func peek() -> Int { + if isEmpty() { + fatalError("キューが空です") + } + return front!.val + } + + /* 連結リストを Array に変換して返す */ + func toArray() -> [Int] { + var node = front + var res = Array(repeating: 0, count: size()) + for i in res.indices { + res[i] = node!.val + node = node?.next + } + return res + } +} + +@main +enum _LinkedListQueue { + /* Driver Code */ + static func main() { + /* キューを初期化 */ + let queue = LinkedListQueue() + + /* 要素をエンキュー */ + queue.push(num: 1) + queue.push(num: 3) + queue.push(num: 2) + queue.push(num: 5) + queue.push(num: 4) + print("キュー queue = \(queue.toArray())") + + /* キュー先頭の要素にアクセス */ + let peek = queue.peek() + print("先頭要素 peek = \(peek)") + + /* 要素をデキュー */ + let pop = queue.pop() + print("取り出した要素 pop = \(pop),取り出し後 queue = \(queue.toArray())") + + /* キューの長さを取得 */ + let size = queue.size() + print("キューのサイズ size = \(size)") + + /* キューが空かどうかを判定 */ + let isEmpty = queue.isEmpty() + print("キューが空かどうか = \(isEmpty)") + } +} diff --git a/ja/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift b/ja/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift new file mode 100644 index 000000000..e8d12eba0 --- /dev/null +++ b/ja/codes/swift/chapter_stack_and_queue/linkedlist_stack.swift @@ -0,0 +1,96 @@ +/** + * File: linkedlist_stack.swift + * Created Time: 2023-01-09 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 連結リストベースのスタック */ +class LinkedListStack { + private var _peek: ListNode? // 先頭ノードをスタックトップとする + private var _size: Int // スタックの長さ + + init() { + _size = 0 + } + + /* スタックの長さを取得 */ + func size() -> Int { + _size + } + + /* スタックが空かどうかを判定 */ + func isEmpty() -> Bool { + size() == 0 + } + + /* プッシュ */ + func push(num: Int) { + let node = ListNode(x: num) + node.next = _peek + _peek = node + _size += 1 + } + + /* ポップ */ + @discardableResult + func pop() -> Int { + let num = peek() + _peek = _peek?.next + _size -= 1 + return num + } + + /* スタックトップの要素にアクセス */ + func peek() -> Int { + if isEmpty() { + fatalError("スタックが空です") + } + return _peek!.val + } + + /* List を Array に変換して返す */ + func toArray() -> [Int] { + var node = _peek + var res = Array(repeating: 0, count: size()) + for i in res.indices.reversed() { + res[i] = node!.val + node = node?.next + } + return res + } +} + +@main +enum _LinkedListStack { + /* Driver Code */ + static func main() { + /* スタックを初期化 */ + let stack = LinkedListStack() + + /* 要素をプッシュ */ + stack.push(num: 1) + stack.push(num: 3) + stack.push(num: 2) + stack.push(num: 5) + stack.push(num: 4) + print("スタック stack = \(stack.toArray())") + + /* スタックトップの要素にアクセス */ + let peek = stack.peek() + print("スタックトップ要素 peek = \(peek)") + + /* 要素をポップ */ + let pop = stack.pop() + print("ポップした要素 pop = \(pop)、ポップ後の stack = \(stack.toArray())") + + /* スタックの長さを取得 */ + let size = stack.size() + print("スタックの長さ size = \(size)") + + /* 空かどうかを判定 */ + let isEmpty = stack.isEmpty() + print("スタックが空かどうか = \(isEmpty)") + } +} diff --git a/ja/codes/swift/chapter_stack_and_queue/queue.swift b/ja/codes/swift/chapter_stack_and_queue/queue.swift new file mode 100644 index 000000000..0dfd94cd9 --- /dev/null +++ b/ja/codes/swift/chapter_stack_and_queue/queue.swift @@ -0,0 +1,40 @@ +/** + * File: queue.swift + * Created Time: 2023-01-11 + * Author: nuomi1 (nuomi1@qq.com) + */ + +@main +enum Queue { + /* Driver Code */ + static func main() { + /* キューを初期化 */ + // Swift には組み込みのキュークラスがないため、Array をキューとして使う + var queue: [Int] = [] + + /* 要素をエンキュー */ + queue.append(1) + queue.append(3) + queue.append(2) + queue.append(5) + queue.append(4) + print("キュー queue = \(queue)") + + /* キュー先頭の要素にアクセス */ + let peek = queue.first! + print("先頭要素 peek = \(peek)") + + /* 要素をデキュー */ + // Array を用いる場合、pop の計算量は O(n) + let pool = queue.removeFirst() + print("デキューした要素 pop = \(pool)、デキュー後の queue = \(queue)") + + /* キューの長さを取得 */ + let size = queue.count + print("キューのサイズ size = \(size)") + + /* キューが空かどうかを判定 */ + let isEmpty = queue.isEmpty + print("キューが空かどうか = \(isEmpty)") + } +} diff --git a/ja/codes/swift/chapter_stack_and_queue/stack.swift b/ja/codes/swift/chapter_stack_and_queue/stack.swift new file mode 100644 index 000000000..2af4357d3 --- /dev/null +++ b/ja/codes/swift/chapter_stack_and_queue/stack.swift @@ -0,0 +1,39 @@ +/** + * File: stack.swift + * Created Time: 2023-01-09 + * Author: nuomi1 (nuomi1@qq.com) + */ + +@main +enum Stack { + /* Driver Code */ + static func main() { + /* スタックを初期化 */ + // Swift には組み込みのスタッククラスがないため、Array をスタックとして使う + var stack: [Int] = [] + + /* 要素をプッシュ */ + stack.append(1) + stack.append(3) + stack.append(2) + stack.append(5) + stack.append(4) + print("スタック stack = \(stack)") + + /* スタックトップの要素にアクセス */ + let peek = stack.last! + print("スタックトップ要素 peek = \(peek)") + + /* 要素をポップ */ + let pop = stack.removeLast() + print("ポップした要素 pop = \(pop)、ポップ後の stack = \(stack)") + + /* スタックの長さを取得 */ + let size = stack.count + print("スタックの長さ size = \(size)") + + /* 空かどうかを判定 */ + let isEmpty = stack.isEmpty + print("スタックが空かどうか = \(isEmpty)") + } +} diff --git a/ja/codes/swift/chapter_tree/array_binary_tree.swift b/ja/codes/swift/chapter_tree/array_binary_tree.swift new file mode 100644 index 000000000..c7db524cb --- /dev/null +++ b/ja/codes/swift/chapter_tree/array_binary_tree.swift @@ -0,0 +1,141 @@ +/** + * File: array_binary_tree.swift + * Created Time: 2023-07-23 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 配列表現による二分木クラス */ +class ArrayBinaryTree { + private var tree: [Int?] + + /* コンストラクタ */ + init(arr: [Int?]) { + tree = arr + } + + /* リスト容量 */ + func size() -> Int { + tree.count + } + + /* インデックス i のノードの値を取得 */ + func val(i: Int) -> Int? { + // インデックスが範囲外なら、空きを表す null を返す + if i < 0 || i >= size() { + return nil + } + return tree[i] + } + + /* インデックス i のノードの左子ノードのインデックスを取得 */ + func left(i: Int) -> Int { + 2 * i + 1 + } + + /* インデックス i のノードの右子ノードのインデックスを取得 */ + func right(i: Int) -> Int { + 2 * i + 2 + } + + /* インデックス i のノードの親ノードのインデックスを取得 */ + func parent(i: Int) -> Int { + (i - 1) / 2 + } + + /* レベル順走査 */ + func levelOrder() -> [Int] { + var res: [Int] = [] + // 配列を直接走査する + for i in 0 ..< size() { + if let val = val(i: i) { + res.append(val) + } + } + return res + } + + /* 深さ優先探索 */ + private func dfs(i: Int, order: String, res: inout [Int]) { + // 空きスロットなら返す + guard let val = val(i: i) else { + return + } + // 先行順走査 + if order == "pre" { + res.append(val) + } + dfs(i: left(i: i), order: order, res: &res) + // 中順走査 + if order == "in" { + res.append(val) + } + dfs(i: right(i: i), order: order, res: &res) + // 後順走査 + if order == "post" { + res.append(val) + } + } + + /* 先行順走査 */ + func preOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "pre", res: &res) + return res + } + + /* 中順走査 */ + func inOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "in", res: &res) + return res + } + + /* 後順走査 */ + func postOrder() -> [Int] { + var res: [Int] = [] + dfs(i: 0, order: "post", res: &res) + return res + } +} + +@main +enum _ArrayBinaryTree { + /* Driver Code */ + static func main() { + // 二分木を初期化 + // ここでは、配列から直接二分木を生成する関数を利用する + let arr = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + + let root = TreeNode.listToTree(arr: arr) + print("\n二分木を初期化\n") + print("二分木の配列表現:") + print(arr) + print("二分木の連結リスト表現:") + PrintUtil.printTree(root: root) + + // 配列表現による二分木クラス + let abt = ArrayBinaryTree(arr: arr) + + // ノードにアクセス + let i = 1 + let l = abt.left(i: i) + let r = abt.right(i: i) + let p = abt.parent(i: i) + print("\n現在のノードのインデックスは \(i) 、値は \(abt.val(i: i) as Any)") + print("左の子ノードのインデックスは \(l) 、値は \(abt.val(i: l) as Any)") + print("右の子ノードのインデックスは \(r) 、値は \(abt.val(i: r) as Any)") + print("親ノードのインデックスは \(p) 、値は \(abt.val(i: p) as Any)") + + // 木を走査 + var res = abt.levelOrder() + print("\nレベル順走査:\(res)") + res = abt.preOrder() + print("先行順走査:\(res)") + res = abt.inOrder() + print("中間順走査:\(res)") + res = abt.postOrder() + print("後順走査は:\(res)") + } +} diff --git a/ja/codes/swift/chapter_tree/avl_tree.swift b/ja/codes/swift/chapter_tree/avl_tree.swift new file mode 100644 index 000000000..212444d28 --- /dev/null +++ b/ja/codes/swift/chapter_tree/avl_tree.swift @@ -0,0 +1,230 @@ +/** + * File: avl_tree.swift + * Created Time: 2023-01-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* AVL 木 */ +class AVLTree { + fileprivate var root: TreeNode? // 根ノード + + init() {} + + /* ノードの高さを取得 */ + func height(node: TreeNode?) -> Int { + // 空ノードの高さは -1、葉ノードの高さは 0 + node?.height ?? -1 + } + + /* ノードの高さを更新する */ + private func updateHeight(node: TreeNode?) { + // ノードの高さは最も高い部分木の高さ + 1 に等しい + node?.height = max(height(node: node?.left), height(node: node?.right)) + 1 + } + + /* 平衡係数を取得 */ + func balanceFactor(node: TreeNode?) -> Int { + // 空ノードの平衡係数は 0 + guard let node = node else { return 0 } + // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ + return height(node: node.left) - height(node: node.right) + } + + /* 右回転 */ + private func rightRotate(node: TreeNode?) -> TreeNode? { + let child = node?.left + let grandChild = child?.right + // child を支点として node を右回転させる + child?.right = node + node?.left = grandChild + // ノードの高さを更新する + updateHeight(node: node) + updateHeight(node: child) + // 回転後の部分木の根ノードを返す + return child + } + + /* 左回転 */ + private func leftRotate(node: TreeNode?) -> TreeNode? { + let child = node?.right + let grandChild = child?.left + // child を支点として node を左回転させる + child?.left = node + node?.right = grandChild + // ノードの高さを更新する + updateHeight(node: node) + updateHeight(node: child) + // 回転後の部分木の根ノードを返す + return child + } + + /* 回転操作を行い、この部分木の平衡を回復する */ + private func rotate(node: TreeNode?) -> TreeNode? { + // ノード node の平衡係数を取得 + let balanceFactor = balanceFactor(node: node) + // 左に偏った木 + if balanceFactor > 1 { + if self.balanceFactor(node: node?.left) >= 0 { + // 右回転 + return rightRotate(node: node) + } else { + // 左回転してから右回転 + node?.left = leftRotate(node: node?.left) + return rightRotate(node: node) + } + } + // 右に偏った木 + if balanceFactor < -1 { + if self.balanceFactor(node: node?.right) <= 0 { + // 左回転 + return leftRotate(node: node) + } else { + // 右回転してから左回転 + node?.right = rightRotate(node: node?.right) + return leftRotate(node: node) + } + } + // 平衡木なので回転不要、そのまま返す + return node + } + + /* ノードを挿入 */ + func insert(val: Int) { + root = insertHelper(node: root, val: val) + } + + /* ノードを再帰的に挿入する(補助メソッド) */ + private func insertHelper(node: TreeNode?, val: Int) -> TreeNode? { + var node = node + if node == nil { + return TreeNode(x: val) + } + /* 1. 挿入位置を探索してノードを挿入 */ + if val < node!.val { + node?.left = insertHelper(node: node?.left, val: val) + } else if val > node!.val { + node?.right = insertHelper(node: node?.right, val: val) + } else { + return node // 重複ノードは挿入せず、そのまま返す + } + updateHeight(node: node) // ノードの高さを更新する + /* 2. 回転操作を行い、部分木の平衡を回復する */ + node = rotate(node: node) + // 部分木の根ノードを返す + return node + } + + /* ノードを削除 */ + func remove(val: Int) { + root = removeHelper(node: root, val: val) + } + + /* ノードを再帰的に削除する(補助メソッド) */ + private func removeHelper(node: TreeNode?, val: Int) -> TreeNode? { + var node = node + if node == nil { + return nil + } + /* 1. ノードを探索して削除 */ + if val < node!.val { + node?.left = removeHelper(node: node?.left, val: val) + } else if val > node!.val { + node?.right = removeHelper(node: node?.right, val: val) + } else { + if node?.left == nil || node?.right == nil { + let child = node?.left ?? node?.right + // 子ノード数 = 0 の場合、node をそのまま削除して返す + if child == nil { + return nil + } + // 子ノード数 = 1 の場合、node をそのまま削除する + else { + node = child + } + } else { + // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える + var temp = node?.right + while temp?.left != nil { + temp = temp?.left + } + node?.right = removeHelper(node: node?.right, val: temp!.val) + node?.val = temp!.val + } + } + updateHeight(node: node) // ノードの高さを更新する + /* 2. 回転操作を行い、部分木の平衡を回復する */ + node = rotate(node: node) + // 部分木の根ノードを返す + return node + } + + /* ノードを探索 */ + func search(val: Int) -> TreeNode? { + var cur = root + while cur != nil { + // 目標ノードは cur の右部分木にある + if cur!.val < val { + cur = cur?.right + } + // 目標ノードは cur の左部分木にある + else if cur!.val > val { + cur = cur?.left + } + // 目標ノードが見つかったらループを抜ける + else { + break + } + } + // 目標ノードを返す + return cur + } +} + +@main +enum _AVLTree { + static func testInsert(tree: AVLTree, val: Int) { + tree.insert(val: val) + print("\nノード \(val) を挿入後、AVL 木は") + PrintUtil.printTree(root: tree.root) + } + + static func testRemove(tree: AVLTree, val: Int) { + tree.remove(val: val) + print("\nノード \(val) を削除後、AVL 木は") + PrintUtil.printTree(root: tree.root) + } + + /* Driver Code */ + static func main() { + /* 空の AVL 木を初期化する */ + let avlTree = AVLTree() + + /* ノードを挿入 */ + // ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい + testInsert(tree: avlTree, val: 1) + testInsert(tree: avlTree, val: 2) + testInsert(tree: avlTree, val: 3) + testInsert(tree: avlTree, val: 4) + testInsert(tree: avlTree, val: 5) + testInsert(tree: avlTree, val: 8) + testInsert(tree: avlTree, val: 7) + testInsert(tree: avlTree, val: 9) + testInsert(tree: avlTree, val: 10) + testInsert(tree: avlTree, val: 6) + + /* 重複ノードを挿入する */ + testInsert(tree: avlTree, val: 7) + + /* ノードを削除 */ + // ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい + testRemove(tree: avlTree, val: 8) // 次数 0 のノードを削除する + testRemove(tree: avlTree, val: 5) // 次数 1 のノードを削除する + testRemove(tree: avlTree, val: 4) // 次数 2 のノードを削除する + + /* ノードを検索 */ + let node = avlTree.search(val: 7) + print("\n見つかったノードオブジェクトは \(node!)、ノード値 = \(node!.val)") + } +} diff --git a/ja/codes/swift/chapter_tree/binary_search_tree.swift b/ja/codes/swift/chapter_tree/binary_search_tree.swift new file mode 100644 index 000000000..7c22e82fa --- /dev/null +++ b/ja/codes/swift/chapter_tree/binary_search_tree.swift @@ -0,0 +1,173 @@ +/** + * File: binary_search_tree.swift + * Created Time: 2023-01-26 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* 二分探索木 */ +class BinarySearchTree { + private var root: TreeNode? + + /* コンストラクタ */ + init() { + // 空の木を初期化する + root = nil + } + + /* 二分木の根ノードを取得 */ + func getRoot() -> TreeNode? { + root + } + + /* ノードを探索 */ + func search(num: Int) -> TreeNode? { + var cur = root + // ループで探索し、葉ノードを越えたら抜ける + while cur != nil { + // 目標ノードは cur の右部分木にある + if cur!.val < num { + cur = cur?.right + } + // 目標ノードは cur の左部分木にある + else if cur!.val > num { + cur = cur?.left + } + // 目標ノードが見つかったらループを抜ける + else { + break + } + } + // 目標ノードを返す + return cur + } + + /* ノードを挿入 */ + func insert(num: Int) { + // 木が空なら、根ノードを初期化する + if root == nil { + root = TreeNode(x: num) + return + } + var cur = root + var pre: TreeNode? + // ループで探索し、葉ノードを越えたら抜ける + while cur != nil { + // 重複ノードが見つかったら、直ちに返す + if cur!.val == num { + return + } + pre = cur + // 挿入位置は cur の右部分木にある + if cur!.val < num { + cur = cur?.right + } + // 挿入位置は cur の左部分木にある + else { + cur = cur?.left + } + } + // ノードを挿入 + let node = TreeNode(x: num) + if pre!.val < num { + pre?.right = node + } else { + pre?.left = node + } + } + + /* ノードを削除 */ + func remove(num: Int) { + // 木が空なら、そのまま早期リターンする + if root == nil { + return + } + var cur = root + var pre: TreeNode? + // ループで探索し、葉ノードを越えたら抜ける + while cur != nil { + // 削除対象のノードが見つかったら、ループを抜ける + if cur!.val == num { + break + } + pre = cur + // 削除対象ノードは cur の右部分木にある + if cur!.val < num { + cur = cur?.right + } + // 削除対象ノードは cur の左部分木にある + else { + cur = cur?.left + } + } + // 削除対象ノードがなければそのまま返す + if cur == nil { + return + } + // 子ノード数 = 0 or 1 + if cur?.left == nil || cur?.right == nil { + // 子ノード数が 0 / 1 のとき、child = null / その子ノード + let child = cur?.left ?? cur?.right + // ノード cur を削除する + if cur !== root { + if pre?.left === cur { + pre?.left = child + } else { + pre?.right = child + } + } else { + // 削除ノードが根ノードなら、根ノードを再設定 + root = child + } + } + // 子ノード数 = 2 + else { + // 中順走査における cur の次ノードを取得 + var tmp = cur?.right + while tmp?.left != nil { + tmp = tmp?.left + } + // ノード tmp を再帰的に削除 + remove(num: tmp!.val) + // tmp で cur を上書きする + cur?.val = tmp!.val + } + } +} + +@main +enum _BinarySearchTree { + /* Driver Code */ + static func main() { + /* 二分探索木を初期化 */ + let bst = BinarySearchTree() + // 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる + let nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15] + for num in nums { + bst.insert(num: num) + } + print("\n初期化された二分木は\n") + PrintUtil.printTree(root: bst.getRoot()) + + /* ノードを探索 */ + let node = bst.search(num: 7) + print("\n見つかったノードオブジェクトは \(node!)、ノード値 = \(node!.val)") + + /* ノードを挿入 */ + bst.insert(num: 16) + print("\nノード 16 を挿入後、二分木は\n") + PrintUtil.printTree(root: bst.getRoot()) + + /* ノードを削除 */ + bst.remove(num: 1) + print("\nノード 1 を削除後、二分木は\n") + PrintUtil.printTree(root: bst.getRoot()) + bst.remove(num: 2) + print("\nノード 2 を削除後、二分木は\n") + PrintUtil.printTree(root: bst.getRoot()) + bst.remove(num: 4) + print("\nノード 4 を削除後、二分木は\n") + PrintUtil.printTree(root: bst.getRoot()) + } +} diff --git a/ja/codes/swift/chapter_tree/binary_tree.swift b/ja/codes/swift/chapter_tree/binary_tree.swift new file mode 100644 index 000000000..83f0fb3ae --- /dev/null +++ b/ja/codes/swift/chapter_tree/binary_tree.swift @@ -0,0 +1,40 @@ +/** + * File: binary_tree.swift + * Created Time: 2023-01-18 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +@main +enum BinaryTree { + /* Driver Code */ + static func main() { + /* 二分木を初期化 */ + // ノードを初期化 + let n1 = TreeNode(x: 1) + let n2 = TreeNode(x: 2) + let n3 = TreeNode(x: 3) + let n4 = TreeNode(x: 4) + let n5 = TreeNode(x: 5) + // ノード間の参照(ポインタ)を構築する + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 + print("\n二分木を初期化\n") + PrintUtil.printTree(root: n1) + + /* ノードの挿入と削除 */ + let P = TreeNode(x: 0) + // n1 -> n2 の間にノード P を挿入 + n1.left = P + P.left = n2 + print("\nノード P を挿入後\n") + PrintUtil.printTree(root: n1) + // ノード P を削除 + n1.left = n2 + print("\nノード P を削除後\n") + PrintUtil.printTree(root: n1) + } +} diff --git a/ja/codes/swift/chapter_tree/binary_tree_bfs.swift b/ja/codes/swift/chapter_tree/binary_tree_bfs.swift new file mode 100644 index 000000000..a99f2833b --- /dev/null +++ b/ja/codes/swift/chapter_tree/binary_tree_bfs.swift @@ -0,0 +1,42 @@ +/** + * File: binary_tree_bfs.swift + * Created Time: 2023-01-18 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +/* レベル順走査 */ +func levelOrder(root: TreeNode) -> [Int] { + // キューを初期化し、ルートノードを追加する + var queue: [TreeNode] = [root] + // 走査順序を保存するためのリストを初期化する + var list: [Int] = [] + while !queue.isEmpty { + let node = queue.removeFirst() // デキュー + list.append(node.val) // ノードの値を保存する + if let left = node.left { + queue.append(left) // 左子ノードをキューに追加 + } + if let right = node.right { + queue.append(right) // 右子ノードをキューに追加 + } + } + return list +} + +@main +enum BinaryTreeBFS { + /* Driver Code */ + static func main() { + /* 二分木を初期化 */ + // ここでは、配列から直接二分木を生成する関数を利用する + let node = TreeNode.listToTree(arr: [1, 2, 3, 4, 5, 6, 7])! + print("\n二分木を初期化\n") + PrintUtil.printTree(root: node) + + /* レベル順走査 */ + let list = levelOrder(root: node) + print("\nレベル順走査のノード出力シーケンス = \(list)") + } +} diff --git a/ja/codes/swift/chapter_tree/binary_tree_dfs.swift b/ja/codes/swift/chapter_tree/binary_tree_dfs.swift new file mode 100644 index 000000000..4b018c090 --- /dev/null +++ b/ja/codes/swift/chapter_tree/binary_tree_dfs.swift @@ -0,0 +1,70 @@ +/** + * File: binary_tree_dfs.swift + * Created Time: 2023-01-18 + * Author: nuomi1 (nuomi1@qq.com) + */ + +import utils + +// 走査順序を格納するリストを初期化 +var list: [Int] = [] + +/* 先行順走査 */ +func preOrder(root: TreeNode?) { + guard let root = root else { + return + } + // 訪問順序:根ノード -> 左部分木 -> 右部分木 + list.append(root.val) + preOrder(root: root.left) + preOrder(root: root.right) +} + +/* 中順走査 */ +func inOrder(root: TreeNode?) { + guard let root = root else { + return + } + // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 + inOrder(root: root.left) + list.append(root.val) + inOrder(root: root.right) +} + +/* 後順走査 */ +func postOrder(root: TreeNode?) { + guard let root = root else { + return + } + // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード + postOrder(root: root.left) + postOrder(root: root.right) + list.append(root.val) +} + +@main +enum BinaryTreeDFS { + /* Driver Code */ + static func main() { + /* 二分木を初期化 */ + // ここでは、配列から直接二分木を生成する関数を利用する + let root = TreeNode.listToTree(arr: [1, 2, 3, 4, 5, 6, 7])! + print("\n二分木を初期化\n") + PrintUtil.printTree(root: root) + + /* 先行順走査 */ + list.removeAll() + preOrder(root: root) + print("\n前順走査のノード出力シーケンス = \(list)") + + /* 中順走査 */ + list.removeAll() + inOrder(root: root) + print("\n中順走査のノード出力シーケンス = \(list)") + + /* 後順走査 */ + list.removeAll() + postOrder(root: root) + print("\n後順走査のノード出力シーケンス = \(list)") + } +} diff --git a/ja/codes/swift/utils/ListNode.swift b/ja/codes/swift/utils/ListNode.swift new file mode 100644 index 000000000..47a7441c1 --- /dev/null +++ b/ja/codes/swift/utils/ListNode.swift @@ -0,0 +1,33 @@ +/** + * File: ListNode.swift + * Created Time: 2023-01-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +public class ListNode: Hashable { + public var val: Int // ノード値 + public var next: ListNode? // 後続ノードへの参照 + + public init(x: Int) { + val = x + } + + public static func == (lhs: ListNode, rhs: ListNode) -> Bool { + lhs.val == rhs.val && lhs.next.map { ObjectIdentifier($0) } == rhs.next.map { ObjectIdentifier($0) } + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(val) + hasher.combine(next.map { ObjectIdentifier($0) }) + } + + public static func arrToLinkedList(arr: [Int]) -> ListNode? { + let dum = ListNode(x: 0) + var head: ListNode? = dum + for val in arr { + head?.next = ListNode(x: val) + head = head?.next + } + return dum.next + } +} diff --git a/ja/codes/swift/utils/Pair.swift b/ja/codes/swift/utils/Pair.swift new file mode 100644 index 000000000..9f4ff2569 --- /dev/null +++ b/ja/codes/swift/utils/Pair.swift @@ -0,0 +1,20 @@ +/** + * File: Pair.swift + * Created Time: 2023-06-28 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* キーと値の組 */ +public class Pair: Equatable { + public var key: Int + public var val: String + + public init(key: Int, val: String) { + self.key = key + self.val = val + } + + public static func == (lhs: Pair, rhs: Pair) -> Bool { + lhs.key == rhs.key && lhs.val == rhs.val + } +} diff --git a/ja/codes/swift/utils/PrintUtil.swift b/ja/codes/swift/utils/PrintUtil.swift new file mode 100644 index 000000000..d8c3f70a1 --- /dev/null +++ b/ja/codes/swift/utils/PrintUtil.swift @@ -0,0 +1,93 @@ +/** + * File: PrintUtil.swift + * Created Time: 2023-01-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +public enum PrintUtil { + private class Trunk { + var prev: Trunk? + var str: String + + init(prev: Trunk?, str: String) { + self.prev = prev + self.str = str + } + } + + public static func printLinkedList(head: ListNode) { + var head: ListNode? = head + var list: [String] = [] + while head != nil { + list.append("\(head!.val)") + head = head?.next + } + print(list.joined(separator: " -> ")) + } + + public static func printTree(root: TreeNode?) { + printTree(root: root, prev: nil, isRight: false) + } + + private static func printTree(root: TreeNode?, prev: Trunk?, isRight: Bool) { + if root == nil { + return + } + + var prevStr = " " + let trunk = Trunk(prev: prev, str: prevStr) + + printTree(root: root?.right, prev: trunk, isRight: true) + + if prev == nil { + trunk.str = "———" + } else if isRight { + trunk.str = "/———" + prevStr = " |" + } else { + trunk.str = "\\———" + prev?.str = prevStr + } + + showTrunks(p: trunk) + print(" \(root!.val)") + + if prev != nil { + prev?.str = prevStr + } + trunk.str = " |" + + printTree(root: root?.left, prev: trunk, isRight: false) + } + + private static func showTrunks(p: Trunk?) { + if p == nil { + return + } + + showTrunks(p: p?.prev) + print(p!.str, terminator: "") + } + + public static func printHashMap(map: [K: V]) { + for (key, value) in map { + print("\(key) -> \(value)") + } + } + + public static func printHeap(queue: [Int]) { + print("ヒープの配列表現:", terminator: "") + print(queue) + print("ヒープの木構造表現:") + let root = TreeNode.listToTree(arr: queue) + printTree(root: root) + } + + public static func printMatrix(matrix: [[T]]) { + print("[") + for row in matrix { + print(" \(row),") + } + print("]") + } +} diff --git a/ja/codes/swift/utils/TreeNode.swift b/ja/codes/swift/utils/TreeNode.swift new file mode 100644 index 000000000..1e8af6ad4 --- /dev/null +++ b/ja/codes/swift/utils/TreeNode.swift @@ -0,0 +1,71 @@ +/** + * File: TreeNode.swift + * Created Time: 2023-01-02 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 二分木ノードクラス */ +public class TreeNode { + public var val: Int // ノード値 + public var height: Int // ノードの高さ + public var left: TreeNode? // 左子ノードへの参照 + public var right: TreeNode? // 右子ノードへの参照 + + /* コンストラクタ */ + public init(x: Int) { + val = x + height = 0 + } + + // シリアライズの符号化規則は次を参照してください: + // https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ + // 二分木の配列表現: + // [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] + // 二分木の連結リスト表現: + // /——— 15 + // /——— 7 + // /——— 3 + // | \\——— 6 + // | \\——— 12 + // ——— 1 + // \\——— 2 + // | /——— 9 + // \\——— 4 + // \\——— 8 + + /* リストを二分木にデシリアライズする: 再帰 */ + private static func listToTreeDFS(arr: [Int?], i: Int) -> TreeNode? { + if i < 0 || i >= arr.count || arr[i] == nil { + return nil + } + let root = TreeNode(x: arr[i]!) + root.left = listToTreeDFS(arr: arr, i: 2 * i + 1) + root.right = listToTreeDFS(arr: arr, i: 2 * i + 2) + return root + } + + /* リストを二分木にデシリアライズする */ + public static func listToTree(arr: [Int?]) -> TreeNode? { + listToTreeDFS(arr: arr, i: 0) + } + + /* 二分木をリストにシリアライズする: 再帰 */ + private static func treeToListDFS(root: TreeNode?, i: Int, res: inout [Int?]) { + if root == nil { + return + } + while i >= res.count { + res.append(nil) + } + res[i] = root?.val + treeToListDFS(root: root?.left, i: 2 * i + 1, res: &res) + treeToListDFS(root: root?.right, i: 2 * i + 2, res: &res) + } + + /* 二分木をリストにシリアライズする */ + public static func treeToList(root: TreeNode?) -> [Int?] { + var res: [Int?] = [] + treeToListDFS(root: root, i: 0, res: &res) + return res + } +} diff --git a/ja/codes/swift/utils/Vertex.swift b/ja/codes/swift/utils/Vertex.swift new file mode 100644 index 000000000..7cf9cbe19 --- /dev/null +++ b/ja/codes/swift/utils/Vertex.swift @@ -0,0 +1,32 @@ +/** + * File: Vertex.swift + * Created Time: 2023-02-19 + * Author: nuomi1 (nuomi1@qq.com) + */ + +/* 頂点クラス */ +public class Vertex: Hashable { + public var val: Int + + public init(val: Int) { + self.val = val + } + + public static func == (lhs: Vertex, rhs: Vertex) -> Bool { + lhs.val == rhs.val + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(val) + } + + /* 値リスト vals を入力し、頂点リスト vets を返す */ + public static func valsToVets(vals: [Int]) -> [Vertex] { + vals.map { Vertex(val: $0) } + } + + /* 頂点リスト vets を入力し、値リスト vals を返す */ + public static func vetsToVals(vets: [Vertex]) -> [Int] { + vets.map { $0.val } + } +} diff --git a/ja/codes/typescript/.gitignore b/ja/codes/typescript/.gitignore new file mode 100644 index 000000000..d5f19d89b --- /dev/null +++ b/ja/codes/typescript/.gitignore @@ -0,0 +1,2 @@ +node_modules +package-lock.json diff --git a/ja/codes/typescript/.prettierrc b/ja/codes/typescript/.prettierrc new file mode 100644 index 000000000..3f4aa8cb6 --- /dev/null +++ b/ja/codes/typescript/.prettierrc @@ -0,0 +1,6 @@ +{ + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true +} diff --git a/ja/codes/typescript/chapter_array_and_linkedlist/array.ts b/ja/codes/typescript/chapter_array_and_linkedlist/array.ts new file mode 100644 index 000000000..513ab8f7d --- /dev/null +++ b/ja/codes/typescript/chapter_array_and_linkedlist/array.ts @@ -0,0 +1,101 @@ +/** + * File: array.ts + * Created Time: 2022-12-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 要素へランダムアクセス */ +function randomAccess(nums: number[]): number { + // 区間 [0, nums.length) からランダムに 1 つの数を選ぶ + const random_index = Math.floor(Math.random() * nums.length); + // ランダムな要素を取得して返す + const random_num = nums[random_index]; + return random_num; +} + +/* 配列長を拡張する */ +// TypeScript の Array は動的配列であり、直接拡張できます +// 学習しやすいよう、本関数では Array を長さ不変の配列として扱います +function extend(nums: number[], enlarge: number): number[] { + // 拡張後の長さを持つ配列を初期化する + const res = new Array(nums.length + enlarge).fill(0); + // 元の配列の全要素を新しい配列にコピー + for (let i = 0; i < nums.length; i++) { + res[i] = nums[i]; + } + // 拡張後の新しい配列を返す + return res; +} + +/* 配列の index 番目に要素 num を挿入 */ +function insert(nums: number[], num: number, index: number): void { + // インデックス index 以降の全要素を 1 つ後ろへ移動する + for (let i = nums.length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // index の要素に num を代入する + nums[index] = num; +} + +/* index の要素を削除する */ +function remove(nums: number[], index: number): void { + // インデックス index より後ろの全要素を 1 つ前へ移動する + for (let i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } +} + +/* 配列を走査 */ +function traverse(nums: number[]): void { + let count = 0; + // インデックスで配列を走査 + for (let i = 0; i < nums.length; i++) { + count += nums[i]; + } + // 配列要素を直接走査 + for (const num of nums) { + count += num; + } +} + +/* 配列内で指定要素を探す */ +function find(nums: number[], target: number): number { + for (let i = 0; i < nums.length; i++) { + if (nums[i] === target) { + return i; + } + } + return -1; +} + +/* Driver Code */ +/* 配列を初期化 */ +const arr: number[] = new Array(5).fill(0); +console.log('配列 arr =', arr); +let nums: number[] = [1, 3, 2, 5, 4]; +console.log('配列 nums =', nums); + +/* ランダムアクセス */ +let random_num = randomAccess(nums); +console.log('nums からランダムな要素を取得', random_num); + +/* 長さを拡張 */ +nums = extend(nums, 3); +console.log('配列の長さを 8 に拡張すると、nums =', nums); + +/* 要素を挿入する */ +insert(nums, 6, 3); +console.log('インデックス 3 に数字 6 を挿入すると、nums =', nums); + +/* 要素を削除 */ +remove(nums, 2); +console.log('インデックス 2 の要素を削除すると、nums =', nums); + +/* 配列を走査 */ +traverse(nums); + +/* 要素を探索する */ +let index = find(nums, 3); +console.log('nums 内で要素 3 を検索すると、インデックス =', index); + +export {}; diff --git a/ja/codes/typescript/chapter_array_and_linkedlist/linked_list.ts b/ja/codes/typescript/chapter_array_and_linkedlist/linked_list.ts new file mode 100644 index 000000000..76ab29570 --- /dev/null +++ b/ja/codes/typescript/chapter_array_and_linkedlist/linked_list.ts @@ -0,0 +1,86 @@ +/** + * File: linked_list.ts + * Created Time: 2022-12-10 + * Author: Justin (xiefahit@gmail.com) + */ + +import { ListNode } from '../modules/ListNode'; +import { printLinkedList } from '../modules/PrintUtil'; + +/* 連結リストでノード n0 の後ろにノード P を挿入する */ +function insert(n0: ListNode, P: ListNode): void { + const n1 = n0.next; + P.next = n1; + n0.next = P; +} + +/* 連結リストでノード n0 の直後のノードを削除する */ +function remove(n0: ListNode): void { + if (!n0.next) { + return; + } + // n0 -> P -> n1 + const P = n0.next; + const n1 = P.next; + n0.next = n1; +} + +/* 連結リスト内で index 番目のノードにアクセス */ +function access(head: ListNode | null, index: number): ListNode | null { + for (let i = 0; i < index; i++) { + if (!head) { + return null; + } + head = head.next; + } + return head; +} + +/* 連結リストで値が target の最初のノードを探す */ +function find(head: ListNode | null, target: number): number { + let index = 0; + while (head !== null) { + if (head.val === target) { + return index; + } + head = head.next; + index += 1; + } + return -1; +} + +/* Driver Code */ +/* 連結リストを初期化 */ +// 各ノードを初期化 +const n0 = new ListNode(1); +const n1 = new ListNode(3); +const n2 = new ListNode(2); +const n3 = new ListNode(5); +const n4 = new ListNode(4); +// ノード間の参照を構築する +n0.next = n1; +n1.next = n2; +n2.next = n3; +n3.next = n4; +console.log('初期化された連結リストは'); +printLinkedList(n0); + +/* ノードを挿入 */ +insert(n0, new ListNode(0)); +console.log('ノード挿入後の連結リストは'); +printLinkedList(n0); + +/* ノードを削除 */ +remove(n0); +console.log('ノード削除後の連結リストは'); +printLinkedList(n0); + +/* ノードにアクセス */ +const node = access(n0, 3); +console.log(`連結リストのインデックス 3 にあるノードの値 = ${node?.val}`); + +/* ノードを探索 */ +const index = find(n0, 2); +console.log(`連結リスト内で値が 2 のノードのインデックス = ${index}`); + +export {}; diff --git a/ja/codes/typescript/chapter_array_and_linkedlist/list.ts b/ja/codes/typescript/chapter_array_and_linkedlist/list.ts new file mode 100644 index 000000000..b201e6d30 --- /dev/null +++ b/ja/codes/typescript/chapter_array_and_linkedlist/list.ts @@ -0,0 +1,59 @@ +/** + * File: list.ts + * Created Time: 2022-12-10 + * Author: Justin (xiefahit@gmail.com) + */ + +/* リストを初期化 */ +const nums: number[] = [1, 3, 2, 5, 4]; +console.log(`リスト nums = ${nums}`); + +/* 要素にアクセス */ +const num: number = nums[1]; +console.log(`インデックス 1 の要素にアクセスすると、num = ${num}`); + +/* 要素を更新 */ +nums[1] = 0; +console.log(`インデックス 1 の要素を 0 に更新すると、nums = ${nums}`); + +/* リストを空にする */ +nums.length = 0; +console.log(`リストを空にすると nums = ${nums}`); + +/* 末尾に要素を追加 */ +nums.push(1); +nums.push(3); +nums.push(2); +nums.push(5); +nums.push(4); +console.log(`要素を追加すると nums = ${nums}`); + +/* 中間に要素を挿入 */ +nums.splice(3, 0, 6); +console.log(`インデックス 3 に数字 6 を挿入すると、nums = ${nums}`); + +/* 要素を削除 */ +nums.splice(3, 1); +console.log(`インデックス 3 の要素を削除すると、nums = ${nums}`); + +/* インデックスでリストを走査 */ +let count = 0; +for (let i = 0; i < nums.length; i++) { + count += nums[i]; +} +/* リスト要素を直接走査 */ +count = 0; +for (const x of nums) { + count += x; +} + +/* 2 つのリストを連結する */ +const nums1: number[] = [6, 8, 7, 10, 9]; +nums.push(...nums1); +console.log(`リスト nums1 を nums の後ろに連結すると、nums = ${nums}`); + +/* リストをソート */ +nums.sort((a, b) => a - b); +console.log(`リストをソートすると nums = ${nums}`); + +export {}; diff --git a/ja/codes/typescript/chapter_array_and_linkedlist/my_list.ts b/ja/codes/typescript/chapter_array_and_linkedlist/my_list.ts new file mode 100644 index 000000000..bbe282688 --- /dev/null +++ b/ja/codes/typescript/chapter_array_and_linkedlist/my_list.ts @@ -0,0 +1,141 @@ +/** + * File: my_list.ts + * Created Time: 2022-12-11 + * Author: Justin (xiefahit@gmail.com) + */ + +/* リストクラス */ +class MyList { + private arr: Array; // 配列(リスト要素を格納) + private _capacity: number = 10; // リスト容量 + private _size: number = 0; // リストの長さ(現在の要素数) + private extendRatio: number = 2; // リスト拡張時の増加倍率 + + /* コンストラクタ */ + constructor() { + this.arr = new Array(this._capacity); + } + + /* リストの長さを取得(現在の要素数) */ + public size(): number { + return this._size; + } + + /* リスト容量を取得する */ + public capacity(): number { + return this._capacity; + } + + /* 要素にアクセス */ + public get(index: number): number { + // インデックスが範囲外なら例外を送出する。以下同様 + if (index < 0 || index >= this._size) throw new Error('インデックスが範囲外です'); + return this.arr[index]; + } + + /* 要素を更新 */ + public set(index: number, num: number): void { + if (index < 0 || index >= this._size) throw new Error('インデックスが範囲外です'); + this.arr[index] = num; + } + + /* 末尾に要素を追加 */ + public add(num: number): void { + // 長さが容量に等しい場合は拡張が必要 + if (this._size === this._capacity) this.extendCapacity(); + // 新しい要素をリストの末尾に追加する + this.arr[this._size] = num; + this._size++; + } + + /* 中間に要素を挿入 */ + public insert(index: number, num: number): void { + if (index < 0 || index >= this._size) throw new Error('インデックスが範囲外です'); + // 要素数が容量を超えると、拡張機構が発動する + if (this._size === this._capacity) { + this.extendCapacity(); + } + // index 以降の要素をすべて 1 つ後ろへずらす + for (let j = this._size - 1; j >= index; j--) { + this.arr[j + 1] = this.arr[j]; + } + // 要素数を更新 + this.arr[index] = num; + this._size++; + } + + /* 要素を削除 */ + public remove(index: number): number { + if (index < 0 || index >= this._size) throw new Error('インデックスが範囲外です'); + let num = this.arr[index]; + // インデックス index より後の要素をすべて 1 つ前に移動する + for (let j = index; j < this._size - 1; j++) { + this.arr[j] = this.arr[j + 1]; + } + // 要素数を更新 + this._size--; + // 削除された要素を返す + return num; + } + + /* リストの拡張 */ + public extendCapacity(): void { + // `size` の長さを持つ配列を新規作成し、元の配列を新しい配列にコピーする + this.arr = this.arr.concat( + new Array(this.capacity() * (this.extendRatio - 1)) + ); + // リストの容量を更新 + this._capacity = this.arr.length; + } + + /* リストを配列に変換する */ + public toArray(): number[] { + let size = this.size(); + // 有効長の範囲内のリスト要素のみを変換 + const arr = new Array(size); + for (let i = 0; i < size; i++) { + arr[i] = this.get(i); + } + return arr; + } +} + +/* Driver Code */ +/* リストを初期化 */ +const nums = new MyList(); +/* 末尾に要素を追加 */ +nums.add(1); +nums.add(3); +nums.add(2); +nums.add(5); +nums.add(4); +console.log( + `リスト nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長さ = ${nums.size()}` +); + +/* 中間に要素を挿入 */ +nums.insert(3, 6); +console.log(`インデックス 3 に数字 6 を挿入すると ,nums = ${nums.toArray()}`); + +/* 要素を削除 */ +nums.remove(3); +console.log(`インデックス 3 の要素を削除すると,nums = ${nums.toArray()}`); + +/* 要素にアクセス */ +const num = nums.get(1); +console.log(`インデックス 1 の要素にアクセスすると、num = ${num}`); + +/* 要素を更新 */ +nums.set(1, 0); +console.log(`インデックス 1 の要素を 0 に更新すると ,nums = ${nums.toArray()}`); + +/* 拡張機構をテストする */ +for (let i = 0; i < 10; i++) { + // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する + nums.add(i); +} +console.log( + `拡張後のリスト nums = ${nums.toArray()} ,容量 = ${nums.capacity()} ,長さ = ${nums.size()}` +); + +export {}; diff --git a/ja/codes/typescript/chapter_backtracking/n_queens.ts b/ja/codes/typescript/chapter_backtracking/n_queens.ts new file mode 100644 index 000000000..1597cdece --- /dev/null +++ b/ja/codes/typescript/chapter_backtracking/n_queens.ts @@ -0,0 +1,65 @@ +/** + * File: n_queens.ts + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* バックトラッキング:N クイーン */ +function backtrack( + row: number, + n: number, + state: string[][], + res: string[][][], + cols: boolean[], + diags1: boolean[], + diags2: boolean[] +): void { + // すべての行への配置が完了したら、解を記録する + if (row === n) { + res.push(state.map((row) => row.slice())); + return; + } + // すべての列を走査 + for (let col = 0; col < n; col++) { + // このマスに対応する主対角線と副対角線を計算 + const diag1 = row - col + n - 1; + const 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; + } + } +} + +/* N クイーンを解く */ +function nQueens(n: number): string[][][] { + // n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す + const state = Array.from({ length: n }, () => Array(n).fill('#')); + const cols = Array(n).fill(false); // 列にクイーンがあるか記録 + const diags1 = Array(2 * n - 1).fill(false); // 主対角線にクイーンがあるかを記録 + const diags2 = Array(2 * n - 1).fill(false); // 副対角線にクイーンがあるかを記録 + const res: string[][][] = []; + + backtrack(0, n, state, res, cols, diags1, diags2); + return res; +} + +// Driver Code +const n = 4; +const res = nQueens(n); + +console.log(`入力する盤面の縦横は ${n}`); +console.log(`クイーンの配置方法は全部で ${res.length} 種`); +res.forEach((state) => { + console.log('--------------------'); + state.forEach((row) => console.log(row)); +}); + +export {}; diff --git a/ja/codes/typescript/chapter_backtracking/permutations_i.ts b/ja/codes/typescript/chapter_backtracking/permutations_i.ts new file mode 100644 index 000000000..e97cfecbc --- /dev/null +++ b/ja/codes/typescript/chapter_backtracking/permutations_i.ts @@ -0,0 +1,49 @@ +/** + * File: permutations_i.ts + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* バックトラッキング:順列 I */ +function backtrack( + state: number[], + choices: number[], + selected: boolean[], + res: number[][] +): void { + // 状態の長さが要素数に等しければ、解を記録 + if (state.length === choices.length) { + res.push([...state]); + return; + } + // すべての選択肢を走査 + choices.forEach((choice, i) => { + // 枝刈り:要素の重複選択を許可しない + if (!selected[i]) { + // 試行: 選択を行い、状態を更新 + selected[i] = true; + state.push(choice); + // 次の選択へ進む + backtrack(state, choices, selected, res); + // バックトラック:選択を取り消し、前の状態に戻す + selected[i] = false; + state.pop(); + } + }); +} + +/* 全順列 I */ +function permutationsI(nums: number[]): number[][] { + const res: number[][] = []; + backtrack([], nums, Array(nums.length).fill(false), res); + return res; +} + +// Driver Code +const nums: number[] = [1, 2, 3]; +const res: number[][] = permutationsI(nums); + +console.log(`入力配列 nums = ${JSON.stringify(nums)}`); +console.log(`すべての順列 res = ${JSON.stringify(res)}`); + +export {}; diff --git a/ja/codes/typescript/chapter_backtracking/permutations_ii.ts b/ja/codes/typescript/chapter_backtracking/permutations_ii.ts new file mode 100644 index 000000000..c38903bde --- /dev/null +++ b/ja/codes/typescript/chapter_backtracking/permutations_ii.ts @@ -0,0 +1,51 @@ +/** + * File: permutations_ii.ts + * Created Time: 2023-05-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* バックトラッキング:順列 II */ +function backtrack( + state: number[], + choices: number[], + selected: boolean[], + res: number[][] +): void { + // 状態の長さが要素数に等しければ、解を記録 + if (state.length === choices.length) { + res.push([...state]); + return; + } + // すべての選択肢を走査 + const duplicated = new Set(); + choices.forEach((choice, i) => { + // 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない + if (!selected[i] && !duplicated.has(choice)) { + // 試行: 選択を行い、状態を更新 + duplicated.add(choice); // 選択済みの要素値を記録 + selected[i] = true; + state.push(choice); + // 次の選択へ進む + backtrack(state, choices, selected, res); + // バックトラック:選択を取り消し、前の状態に戻す + selected[i] = false; + state.pop(); + } + }); +} + +/* 全順列 II */ +function permutationsII(nums: number[]): number[][] { + const res: number[][] = []; + backtrack([], nums, Array(nums.length).fill(false), res); + return res; +} + +// Driver Code +const nums: number[] = [1, 2, 2]; +const res: number[][] = permutationsII(nums); + +console.log(`入力配列 nums = ${JSON.stringify(nums)}`); +console.log(`すべての順列 res = ${JSON.stringify(res)}`); + +export {}; diff --git a/ja/codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts b/ja/codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts new file mode 100644 index 000000000..08bc9951a --- /dev/null +++ b/ja/codes/typescript/chapter_backtracking/preorder_traversal_i_compact.ts @@ -0,0 +1,36 @@ +/** + * File: preorder_traversal_i_compact.ts + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 前順走査:例題 1 */ +function preOrder(root: TreeNode | null, res: TreeNode[]): void { + if (root === null) { + return; + } + if (root.val === 7) { + // 解を記録 + res.push(root); + } + preOrder(root.left, res); + preOrder(root.right, res); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n二分木を初期化'); +printTree(root); + +// 先行順走査 +const res: TreeNode[] = []; +preOrder(root, res); + +console.log('\n値が 7 のノードをすべて出力'); +console.log(res.map((node) => node.val)); + +export {}; diff --git a/ja/codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts b/ja/codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts new file mode 100644 index 000000000..d4560738a --- /dev/null +++ b/ja/codes/typescript/chapter_backtracking/preorder_traversal_ii_compact.ts @@ -0,0 +1,47 @@ +/** + * File: preorder_traversal_ii_compact.ts + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 前順走査:例題 2 */ +function preOrder( + root: TreeNode | null, + path: TreeNode[], + res: TreeNode[][] +): void { + if (root === null) { + return; + } + // 試す + path.push(root); + if (root.val === 7) { + // 解を記録 + res.push([...path]); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // バックトラック + path.pop(); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n二分木を初期化'); +printTree(root); + +// 先行順走査 +const path: TreeNode[] = []; +const res: TreeNode[][] = []; +preOrder(root, path, res); + +console.log('\nすべての根ノードからノード 7 への経路を出力'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); + +export {}; diff --git a/ja/codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts b/ja/codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts new file mode 100644 index 000000000..ce9d62c26 --- /dev/null +++ b/ja/codes/typescript/chapter_backtracking/preorder_traversal_iii_compact.ts @@ -0,0 +1,48 @@ +/** + * File: preorder_traversal_iii_compact.ts + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 前順走査:例題 3 */ +function preOrder( + root: TreeNode | null, + path: TreeNode[], + res: TreeNode[][] +): void { + // 枝刈り + if (root === null || root.val === 3) { + return; + } + // 試す + path.push(root); + if (root.val === 7) { + // 解を記録 + res.push([...path]); + } + preOrder(root.left, path, res); + preOrder(root.right, path, res); + // バックトラック + path.pop(); +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n二分木を初期化'); +printTree(root); + +// 先行順走査 +const path: TreeNode[] = []; +const res: TreeNode[][] = []; +preOrder(root, path, res); + +console.log('\nすべての根ノードからノード 7 への経路を出力,経路に値が 3 のノードを含まない'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); + +export {}; diff --git a/ja/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts b/ja/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts new file mode 100644 index 000000000..d60bf6649 --- /dev/null +++ b/ja/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts @@ -0,0 +1,75 @@ +/** + * File: preorder_traversal_iii_template.ts + * Created Time: 2023-05-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 現在の状態が解かどうかを判定 */ +function isSolution(state: TreeNode[]): boolean { + return state && state[state.length - 1]?.val === 7; +} + +/* 解を記録 */ +function recordSolution(state: TreeNode[], res: TreeNode[][]): void { + res.push([...state]); +} + +/* 現在の状態で、この選択が有効かどうかを判定 */ +function isValid(state: TreeNode[], choice: TreeNode): boolean { + return choice !== null && choice.val !== 3; +} + +/* 状態を更新 */ +function makeChoice(state: TreeNode[], choice: TreeNode): void { + state.push(choice); +} + +/* 状態を元に戻す */ +function undoChoice(state: TreeNode[]): void { + state.pop(); +} + +/* バックトラッキング:例題 3 */ +function backtrack( + state: TreeNode[], + choices: TreeNode[], + res: TreeNode[][] +): void { + // 解かどうかを確認 + if (isSolution(state)) { + // 解を記録 + recordSolution(state, res); + } + // すべての選択肢を走査 + for (const choice of choices) { + // 枝刈り:選択が妥当かを確認する + if (isValid(state, choice)) { + // 試行: 選択を行い、状態を更新 + makeChoice(state, choice); + // 次の選択へ進む + backtrack(state, [choice.left, choice.right], res); + // バックトラック:選択を取り消し、前の状態に戻す + undoChoice(state); + } + } +} + +// Driver Code +const root = arrToTree([1, 7, 3, 4, 5, 6, 7]); +console.log('\n二分木を初期化'); +printTree(root); + +// バックトラッキング法 +const res: TreeNode[][] = []; +backtrack([], [root], res); + +console.log('\nすべての根ノードからノード 7 への経路を出力,経路に値が 3 のノードを含まないことを条件とする'); +res.forEach((path) => { + console.log(path.map((node) => node.val)); +}); + +export {}; diff --git a/ja/codes/typescript/chapter_backtracking/subset_sum_i.ts b/ja/codes/typescript/chapter_backtracking/subset_sum_i.ts new file mode 100644 index 000000000..02e373a32 --- /dev/null +++ b/ja/codes/typescript/chapter_backtracking/subset_sum_i.ts @@ -0,0 +1,54 @@ +/** + * File: subset_sum_i.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* バックトラッキング:部分和 I */ +function backtrack( + state: number[], + target: number, + choices: number[], + start: number, + res: number[][] +): void { + // 部分集合の和が target に等しければ、解を記録 + if (target === 0) { + res.push([...state]); + return; + } + // すべての選択肢を走査 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + for (let i = start; i < choices.length; i++) { + // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため + if (target - choices[i] < 0) { + break; + } + // 試す:選択を行い、target と start を更新 + state.push(choices[i]); + // 次の選択へ進む + backtrack(state, target - choices[i], choices, i, res); + // バックトラック:選択を取り消し、前の状態に戻す + state.pop(); + } +} + +/* 部分和 I を解く */ +function subsetSumI(nums: number[], target: number): number[][] { + const state = []; // 状態(部分集合) + nums.sort((a, b) => a - b); // nums をソート + const start = 0; // 開始点を走査 + const res = []; // 結果リスト(部分集合のリスト) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +const nums = [3, 4, 5]; +const target = 9; +const res = subsetSumI(nums, target); +console.log(`入力配列 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`合計が ${target} に等しいすべての部分集合 res = ${JSON.stringify(res)}`); + +export {}; diff --git a/ja/codes/typescript/chapter_backtracking/subset_sum_i_naive.ts b/ja/codes/typescript/chapter_backtracking/subset_sum_i_naive.ts new file mode 100644 index 000000000..f59eb1b6a --- /dev/null +++ b/ja/codes/typescript/chapter_backtracking/subset_sum_i_naive.ts @@ -0,0 +1,52 @@ +/** + * File: subset_sum_i_naive.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* バックトラッキング:部分和 I */ +function backtrack( + state: number[], + target: number, + total: number, + choices: number[], + res: number[][] +): void { + // 部分集合の和が target に等しければ、解を記録 + if (total === target) { + res.push([...state]); + return; + } + // すべての選択肢を走査 + for (let i = 0; i < choices.length; i++) { + // 枝刈り:部分和が target を超える場合はその選択をスキップする + if (total + choices[i] > target) { + continue; + } + // 試行:選択を行い、要素と total を更新する + state.push(choices[i]); + // 次の選択へ進む + backtrack(state, target, total + choices[i], choices, res); + // バックトラック:選択を取り消し、前の状態に戻す + state.pop(); + } +} + +/* 部分和 I を解く(重複部分集合を含む) */ +function subsetSumINaive(nums: number[], target: number): number[][] { + const state = []; // 状態(部分集合) + const total = 0; // 部分和 + const res = []; // 結果リスト(部分集合のリスト) + backtrack(state, target, total, nums, res); + return res; +} + +/* Driver Code */ +const nums = [3, 4, 5]; +const target = 9; +const res = subsetSumINaive(nums, target); +console.log(`入力配列 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`合計が ${target} に等しいすべての部分集合 res = ${JSON.stringify(res)}`); +console.log('この方法で出力される結果には重複する集合が含まれます'); + +export {}; diff --git a/ja/codes/typescript/chapter_backtracking/subset_sum_ii.ts b/ja/codes/typescript/chapter_backtracking/subset_sum_ii.ts new file mode 100644 index 000000000..54397cc16 --- /dev/null +++ b/ja/codes/typescript/chapter_backtracking/subset_sum_ii.ts @@ -0,0 +1,59 @@ +/** + * File: subset_sum_ii.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* バックトラッキング:部分和 II */ +function backtrack( + state: number[], + target: number, + choices: number[], + start: number, + res: number[][] +): void { + // 部分集合の和が target に等しければ、解を記録 + if (target === 0) { + res.push([...state]); + return; + } + // すべての選択肢を走査 + // 枝刈り 2: start から走査し、重複する部分集合の生成を避ける + // 枝刈り 3: start から走査し、同じ要素の重複選択を避ける + for (let i = start; i < choices.length; i++) { + // 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する + // 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため + if (target - choices[i] < 0) { + break; + } + // 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする + if (i > start && choices[i] === choices[i - 1]) { + continue; + } + // 試す:選択を行い、target と start を更新 + state.push(choices[i]); + // 次の選択へ進む + backtrack(state, target - choices[i], choices, i + 1, res); + // バックトラック:選択を取り消し、前の状態に戻す + state.pop(); + } +} + +/* 部分和 II を解く */ +function subsetSumII(nums: number[], target: number): number[][] { + const state = []; // 状態(部分集合) + nums.sort((a, b) => a - b); // nums をソート + const start = 0; // 開始点を走査 + const res = []; // 結果リスト(部分集合のリスト) + backtrack(state, target, nums, start, res); + return res; +} + +/* Driver Code */ +const nums = [4, 4, 5]; +const target = 9; +const res = subsetSumII(nums, target); +console.log(`入力配列 nums = ${JSON.stringify(nums)}, target = ${target}`); +console.log(`合計が ${target} に等しいすべての部分集合 res = ${JSON.stringify(res)}`); + +export {}; diff --git a/ja/codes/typescript/chapter_computational_complexity/iteration.ts b/ja/codes/typescript/chapter_computational_complexity/iteration.ts new file mode 100644 index 000000000..be221db6d --- /dev/null +++ b/ja/codes/typescript/chapter_computational_complexity/iteration.ts @@ -0,0 +1,72 @@ +/** + * File: iteration.ts + * Created Time: 2023-08-28 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* for ループ */ +function forLoop(n: number): number { + let res = 0; + // 1, 2, ..., n-1, n を順に加算する + for (let i = 1; i <= n; i++) { + res += i; + } + return res; +} + +/* while ループ */ +function whileLoop(n: number): number { + let res = 0; + let i = 1; // 条件変数を初期化する + // 1, 2, ..., n-1, n を順に加算する + while (i <= n) { + res += i; + i++; // 条件変数を更新する + } + return res; +} + +/* while ループ(2回更新) */ +function whileLoopII(n: number): number { + let res = 0; + let i = 1; // 条件変数を初期化する + // 1, 4, 10, ... を順に加算する + while (i <= n) { + res += i; + // 条件変数を更新する + i++; + i *= 2; + } + return res; +} + +/* 二重 for ループ */ +function nestedForLoop(n: number): string { + let res = ''; + // i = 1, 2, ..., n-1, n とループする + for (let i = 1; i <= n; i++) { + // j = 1, 2, ..., n-1, n とループする + for (let j = 1; j <= n; j++) { + res += `(${i}, ${j}), `; + } + } + return res; +} + +/* Driver Code */ +const n = 5; +let res: number; + +res = forLoop(n); +console.log(`for ループの合計結果 res = ${res}`); + +res = whileLoop(n); +console.log(`while ループの合計結果 res = ${res}`); + +res = whileLoopII(n); +console.log(`while ループ(2 回更新)の合計結果 res = ${res}`); + +const resStr = nestedForLoop(n); +console.log(`二重 for ループの走査結果 ${resStr}`); + +export {}; diff --git a/ja/codes/typescript/chapter_computational_complexity/recursion.ts b/ja/codes/typescript/chapter_computational_complexity/recursion.ts new file mode 100644 index 000000000..1127993f0 --- /dev/null +++ b/ja/codes/typescript/chapter_computational_complexity/recursion.ts @@ -0,0 +1,70 @@ +/** + * File: recursion.ts + * Created Time: 2023-08-28 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 再帰 */ +function recur(n: number): number { + // 終了条件 + if (n === 1) return 1; + // 再帰:再帰呼び出し + const res = recur(n - 1); + // 帰りがけ:結果を返す + return n + res; +} + +/* 反復で再帰を模擬する */ +function forLoopRecur(n: number): number { + // 明示的なスタックを使ってシステムコールスタックを模擬する + const stack: number[] = []; + let res: number = 0; + // 再帰:再帰呼び出し + for (let i = n; i > 0; i--) { + // 「スタックへのプッシュ」で「再帰」を模擬する + stack.push(i); + } + // 帰りがけ:結果を返す + while (stack.length) { + // 「スタックから取り出す操作」で「帰り」をシミュレート + res += stack.pop(); + } + // res = 1+2+3+...+n + return res; +} + +/* 末尾再帰 */ +function tailRecur(n: number, res: number): number { + // 終了条件 + if (n === 0) return res; + // 末尾再帰呼び出し + return tailRecur(n - 1, res + n); +} + +/* フィボナッチ数列:再帰 */ +function fib(n: number): number { + // 終了条件 f(1) = 0, f(2) = 1 + if (n === 1 || n === 2) return n - 1; + // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す + const res = fib(n - 1) + fib(n - 2); + // 結果 f(n) を返す + return res; +} + +/* Driver Code */ +const n = 5; +let res: number; + +res = recur(n); +console.log(`再帰関数の合計結果 res = ${res}`); + +res = forLoopRecur(n); +console.log(`反復で再帰をシミュレートした合計結果 res = ${res}`); + +res = tailRecur(n, 0); +console.log(`末尾再帰関数の合計結果 res = ${res}`); + +res = fib(n); +console.log(`フィボナッチ数列の第 ${n} 項は ${res}`); + +export {}; diff --git a/ja/codes/typescript/chapter_computational_complexity/space_complexity.ts b/ja/codes/typescript/chapter_computational_complexity/space_complexity.ts new file mode 100644 index 000000000..57acedfa9 --- /dev/null +++ b/ja/codes/typescript/chapter_computational_complexity/space_complexity.ts @@ -0,0 +1,103 @@ +/** + * File: space_complexity.ts + * Created Time: 2023-02-05 + * Author: Justin (xiefahit@gmail.com) + */ + +import { ListNode } from '../modules/ListNode'; +import { TreeNode } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 関数 */ +function constFunc(): number { + // 何らかの処理を行う + return 0; +} + +/* 定数階 */ +function constant(n: number): void { + // 定数、変数、オブジェクトは O(1) の空間を占める + const a = 0; + const b = 0; + const nums = new Array(10000); + const node = new ListNode(0); + // ループ内の変数は O(1) の空間を占める + for (let i = 0; i < n; i++) { + const c = 0; + } + // ループ内の関数は O(1) の空間を占める + for (let i = 0; i < n; i++) { + constFunc(); + } +} + +/* 線形階 */ +function linear(n: number): void { + // 長さ n の配列は O(n) の空間を使用 + const nums = new Array(n); + // 長さ n のリストは O(n) の空間を使用 + const nodes: ListNode[] = []; + for (let i = 0; i < n; i++) { + nodes.push(new ListNode(i)); + } + // 長さ n のハッシュテーブルは O(n) の空間を使用 + const map = new Map(); + for (let i = 0; i < n; i++) { + map.set(i, i.toString()); + } +} + +/* 線形時間(再帰実装) */ +function linearRecur(n: number): void { + console.log(`再帰 n = ${n}`); + if (n === 1) return; + linearRecur(n - 1); +} + +/* 二乗階 */ +function quadratic(n: number): void { + // 行列は O(n^2) の空間を使用する + const numMatrix = Array(n) + .fill(null) + .map(() => Array(n).fill(null)); + // 二次元リストは O(n^2) の空間を使用 + const numList = []; + for (let i = 0; i < n; i++) { + const tmp = []; + for (let j = 0; j < n; j++) { + tmp.push(0); + } + numList.push(tmp); + } +} + +/* 二次時間(再帰実装) */ +function quadraticRecur(n: number): number { + if (n <= 0) return 0; + const nums = new Array(n); + console.log(`再帰 n = ${n} における nums の長さ = ${nums.length}`); + return quadraticRecur(n - 1); +} + +/* 指数時間(完全二分木の構築) */ +function buildTree(n: number): TreeNode | null { + if (n === 0) return null; + const root = new TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; +} + +/* Driver Code */ +const n = 5; +// 定数階 +constant(n); +// 線形階 +linear(n); +linearRecur(n); +// 二乗階 +quadratic(n); +quadraticRecur(n); +// 指数オーダー +const root = buildTree(n); +printTree(root); diff --git a/ja/codes/typescript/chapter_computational_complexity/time_complexity.ts b/ja/codes/typescript/chapter_computational_complexity/time_complexity.ts new file mode 100644 index 000000000..62b985091 --- /dev/null +++ b/ja/codes/typescript/chapter_computational_complexity/time_complexity.ts @@ -0,0 +1,157 @@ +/** + * File: time_complexity.ts + * Created Time: 2023-01-02 + * Author: RiverTwilight (contact@rene.wang) + */ + +/* 定数階 */ +function constant(n: number): number { + let count = 0; + const size = 100000; + for (let i = 0; i < size; i++) count++; + return count; +} + +/* 線形階 */ +function linear(n: number): number { + let count = 0; + for (let i = 0; i < n; i++) count++; + return count; +} + +/* 線形時間(配列を走査) */ +function arrayTraversal(nums: number[]): number { + let count = 0; + // ループ回数は配列長に比例する + for (let i = 0; i < nums.length; i++) { + count++; + } + return count; +} + +/* 二乗階 */ +function quadratic(n: number): number { + let count = 0; + // ループ回数はデータサイズ n の二乗に比例する + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + count++; + } + } + return count; +} + +/* 二次時間(バブルソート) */ +function bubbleSort(nums: number[]): number { + let count = 0; // カウンタ + // 外側のループ:未ソート区間は [0, i] + for (let i = nums.length - 1; i > 0; i--) { + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // nums[j] と nums[j + 1] を交換 + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 要素交換には 3 回の単位操作が含まれる + } + } + } + return count; +} + +/* 指数時間(ループ実装) */ +function exponential(n: number): number { + let count = 0, + base = 1; + // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する + for (let i = 0; i < n; i++) { + for (let j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +/* 指数時間(再帰実装) */ +function expRecur(n: number): number { + if (n === 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +/* 対数時間(ループ実装) */ +function logarithmic(n: number): number { + let count = 0; + while (n > 1) { + n = n / 2; + count++; + } + return count; +} + +/* 対数時間(再帰実装) */ +function logRecur(n: number): number { + if (n <= 1) return 0; + return logRecur(n / 2) + 1; +} + +/* 線形対数時間 */ +function linearLogRecur(n: number): number { + if (n <= 1) return 1; + let count = linearLogRecur(n / 2) + linearLogRecur(n / 2); + for (let i = 0; i < n; i++) { + count++; + } + return count; +} + +/* 階乗時間(再帰実装) */ +function factorialRecur(n: number): number { + if (n === 0) return 1; + let count = 0; + // 1個から n 個に分裂 + for (let i = 0; i < n; i++) { + count += factorialRecur(n - 1); + } + return count; +} + +/* Driver Code */ +// n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる +const n = 8; +console.log('入力データサイズ n = ' + n); + +let count = constant(n); +console.log('定数時間の操作回数 = ' + count); + +count = linear(n); +console.log('線形時間の操作回数 = ' + count); +count = arrayTraversal(new Array(n)); +console.log('線形時間(配列の走査)の操作回数 = ' + count); + +count = quadratic(n); +console.log('二乗時間の操作回数 = ' + count); +var nums = new Array(n); +for (let i = 0; i < n; i++) nums[i] = n - i; // [n,n-1,...,2,1] +count = bubbleSort(nums); +console.log('二乗時間(バブルソート)の操作回数 = ' + count); + +count = exponential(n); +console.log('指数時間(ループ実装)の操作回数 = ' + count); +count = expRecur(n); +console.log('指数時間(再帰実装)の操作回数 = ' + count); + +count = logarithmic(n); +console.log('対数時間(ループ実装)の操作回数 = ' + count); +count = logRecur(n); +console.log('対数時間(再帰実装)の操作回数 = ' + count); + +count = linearLogRecur(n); +console.log('線形対数時間(再帰実装)の操作回数 = ' + count); + +count = factorialRecur(n); +console.log('階乗時間(再帰実装)の操作回数 = ' + count); + +export {}; diff --git a/ja/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts b/ja/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts new file mode 100644 index 000000000..04ae61c15 --- /dev/null +++ b/ja/codes/typescript/chapter_computational_complexity/worst_best_time_complexity.ts @@ -0,0 +1,45 @@ +/** + * File: worst_best_time_complexity.ts + * Created Time: 2023-01-05 + * Author: RiverTwilight (contact@rene.wang) + */ + +/* 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 */ +function randomNumbers(n: number): number[] { + const nums = Array(n); + // 配列 nums = { 1, 2, 3, ..., n } を生成 + for (let i = 0; i < n; i++) { + nums[i] = i + 1; + } + // 配列要素をランダムにシャッフル + for (let i = 0; i < n; i++) { + const r = Math.floor(Math.random() * (i + 1)); + const temp = nums[i]; + nums[i] = nums[r]; + nums[r] = temp; + } + return nums; +} + +/* 配列 nums 内で数値 1 のインデックスを探す */ +function findOne(nums: number[]): number { + for (let i = 0; i < nums.length; i++) { + // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる + // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる + if (nums[i] === 1) { + return i; + } + } + return -1; +} + +/* Driver Code */ +for (let i = 0; i < 10; i++) { + const n = 100; + const nums = randomNumbers(n); + const index = findOne(nums); + console.log('\n配列 [ 1, 2, ..., n ] をシャッフルした後 = [' + nums.join(', ') + ']'); + console.log('数字 1 のインデックスは ' + index); +} + +export {}; diff --git a/ja/codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts b/ja/codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts new file mode 100644 index 000000000..e1f32514a --- /dev/null +++ b/ja/codes/typescript/chapter_divide_and_conquer/binary_search_recur.ts @@ -0,0 +1,41 @@ +/** + * File: binary_search_recur.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 二分探索:問題 f(i, j) */ +function dfs(nums: number[], target: number, i: number, j: number): number { + // 区間が空なら対象要素は存在しないので -1 を返す + if (i > j) { + return -1; + } + // 中点インデックス m を計算 + const m = i + ((j - i) >> 1); + if (nums[m] < target) { + // 部分問題 f(m+1, j) を再帰的に解く + return dfs(nums, target, m + 1, j); + } else if (nums[m] > target) { + // 部分問題 f(i, m-1) を再帰的に解く + return dfs(nums, target, i, m - 1); + } else { + // 目標要素が見つかったらそのインデックスを返す + return m; + } +} + +/* 二分探索 */ +function binarySearch(nums: number[], target: number): number { + const n = nums.length; + // 問題 f(0, n-1) を解く + return dfs(nums, target, 0, n - 1); +} + +/* Driver Code */ +const target = 6; +const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; +// 二分探索(両閉区間) +const index = binarySearch(nums, target); +console.log(`対象要素 6 のインデックス = ${index}`); + +export {}; diff --git a/ja/codes/typescript/chapter_divide_and_conquer/build_tree.ts b/ja/codes/typescript/chapter_divide_and_conquer/build_tree.ts new file mode 100644 index 000000000..644768450 --- /dev/null +++ b/ja/codes/typescript/chapter_divide_and_conquer/build_tree.ts @@ -0,0 +1,50 @@ +/** + * File: build_tree.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +import { printTree } from '../modules/PrintUtil'; +import { TreeNode } from '../modules/TreeNode'; + +/* 二分木を構築:分割統治 */ +function dfs( + preorder: number[], + inorderMap: Map, + i: number, + l: number, + r: number +): TreeNode | null { + // 部分木区間が空なら終了する + if (r - l < 0) return null; + // ルートノードを初期化する + const root: TreeNode = new TreeNode(preorder[i]); + // m を求めて左右部分木を分割する + const m = inorderMap.get(preorder[i]); + // 部分問題:左部分木を構築する + root.left = dfs(preorder, inorderMap, i + 1, l, m - 1); + // 部分問題:右部分木を構築する + root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r); + // 根ノードを返す + return root; +} + +/* 二分木を構築 */ +function buildTree(preorder: number[], inorder: number[]): TreeNode | null { + // inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する + let inorderMap = new Map(); + for (let i = 0; i < inorder.length; i++) { + inorderMap.set(inorder[i], i); + } + const root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; +} + +/* Driver Code */ +const preorder = [3, 9, 2, 1, 7]; +const inorder = [9, 3, 1, 2, 7]; +console.log('前順走査 = ' + JSON.stringify(preorder)); +console.log('中順走査 = ' + JSON.stringify(inorder)); +const root = buildTree(preorder, inorder); +console.log('構築した二分木:'); +printTree(root); diff --git a/ja/codes/typescript/chapter_divide_and_conquer/hanota.ts b/ja/codes/typescript/chapter_divide_and_conquer/hanota.ts new file mode 100644 index 000000000..664fd9909 --- /dev/null +++ b/ja/codes/typescript/chapter_divide_and_conquer/hanota.ts @@ -0,0 +1,52 @@ +/** + * File: hanota.ts + * Created Time: 2023-07-30 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 円盤を 1 枚移動 */ +function move(src: number[], tar: number[]): void { + // src の上から円盤を1枚取り出す + const pan = src.pop(); + // 円盤を tar の上に置く + tar.push(pan); +} + +/* ハノイの塔の問題 f(i) を解く */ +function dfs(i: number, src: number[], buf: number[], tar: number[]): void { + // src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す + if (i === 1) { + move(src, tar); + return; + } + // 部分問題 f(i-1):src の上部 i-1 枚の円盤を tar を補助にして buf へ移す + dfs(i - 1, src, tar, buf); + // 部分問題 f(1):src に残る 1 枚の円盤を tar に移す + move(src, tar); + // 部分問題 f(i-1):buf の上部 i-1 枚の円盤を src を補助にして tar へ移す + dfs(i - 1, buf, src, tar); +} + +/* ハノイの塔を解く */ +function solveHanota(A: number[], B: number[], C: number[]): void { + const n = A.length; + // A の上から n 枚の円盤を B を介して C へ移す + dfs(n, A, B, C); +} + +/* Driver Code */ +// リスト末尾が柱の頂上 +const A = [5, 4, 3, 2, 1]; +const B = []; +const C = []; +console.log('初期状態:'); +console.log(`A = ${JSON.stringify(A)}`); +console.log(`B = ${JSON.stringify(B)}`); +console.log(`C = ${JSON.stringify(C)}`); + +solveHanota(A, B, C); + +console.log('円盤の移動完了後:'); +console.log(`A = ${JSON.stringify(A)}`); +console.log(`B = ${JSON.stringify(B)}`); +console.log(`C = ${JSON.stringify(C)}`); diff --git a/ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts b/ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts new file mode 100644 index 000000000..f55ec4a19 --- /dev/null +++ b/ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_backtrack.ts @@ -0,0 +1,41 @@ +/** + * File: climbing_stairs_backtrack.ts + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* バックトラッキング */ +function backtrack( + choices: number[], + state: number, + n: number, + res: Map<0, any> +): void { + // 第 n 段に到達したら、方法数を 1 増やす + if (state === n) res.set(0, res.get(0) + 1); + // すべての選択肢を走査 + for (const choice of choices) { + // 枝刈り: 第 n 段を超えないようにする + if (state + choice > n) continue; + // 試行: 選択を行い、状態を更新 + backtrack(choices, state + choice, n, res); + // バックトラック + } +} + +/* 階段登り:バックトラッキング */ +function climbingStairsBacktrack(n: number): number { + const choices = [1, 2]; // 1 段または 2 段上ることを選べる + const state = 0; // 第 0 段から上り始める + const res = new Map(); + res.set(0, 0); // res[0] を使って方法数を記録する + backtrack(choices, state, n, res); + return res.get(0); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsBacktrack(n); +console.log(`${n} 段の階段を登る方法は ${res} 通り`); + +export {}; diff --git a/ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts b/ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts new file mode 100644 index 000000000..8daae7e9d --- /dev/null +++ b/ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_constraint_dp.ts @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_constraint_dp.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 制約付き階段登り:動的計画法 */ +function climbingStairsConstraintDP(n: number): number { + if (n === 1 || n === 2) { + return 1; + } + // 部分問題の解を保存するために dp テーブルを初期化 + const dp = Array.from({ length: n + 1 }, () => new Array(3)); + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for (let i = 3; i <= n; i++) { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsConstraintDP(n); +console.log(`${n} 段の階段を登る方法は ${res} 通り`); + +export {}; diff --git a/ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts b/ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts new file mode 100644 index 000000000..0dd892615 --- /dev/null +++ b/ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs.ts @@ -0,0 +1,26 @@ +/** + * File: climbing_stairs_dfs.ts + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 検索 */ +function dfs(i: number): number { + // dp[1] と dp[2] は既知なので返す + if (i === 1 || i === 2) return i; + // dp[i] = dp[i-1] + dp[i-2] + const count = dfs(i - 1) + dfs(i - 2); + return count; +} + +/* 階段登り:探索 */ +function climbingStairsDFS(n: number): number { + return dfs(n); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsDFS(n); +console.log(`${n} 段の階段を登る方法は ${res} 通り`); + +export {}; diff --git a/ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts b/ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts new file mode 100644 index 000000000..36934cfa6 --- /dev/null +++ b/ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_dfs_mem.ts @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs_mem.ts + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* メモ化探索 */ +function dfs(i: number, mem: number[]): number { + // dp[1] と dp[2] は既知なので返す + if (i === 1 || i === 2) return i; + // dp[i] の記録があれば、それをそのまま返す + if (mem[i] != -1) return mem[i]; + // dp[i] = dp[i-1] + dp[i-2] + const count = dfs(i - 1, mem) + dfs(i - 2, mem); + // dp[i] を記録する + mem[i] = count; + return count; +} + +/* 階段登り:メモ化探索 */ +function climbingStairsDFSMem(n: number): number { + // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す + const mem = new Array(n + 1).fill(-1); + return dfs(n, mem); +} + +/* Driver Code */ +const n = 9; +const res = climbingStairsDFSMem(n); +console.log(`${n} 段の階段を登る方法は ${res} 通り`); + +export {}; diff --git a/ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts b/ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts new file mode 100644 index 000000000..7a8111b8d --- /dev/null +++ b/ja/codes/typescript/chapter_dynamic_programming/climbing_stairs_dp.ts @@ -0,0 +1,42 @@ +/** + * File: climbing_stairs_dp.ts + * Created Time: 2023-07-26 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 階段登り:動的計画法 */ +function climbingStairsDP(n: number): number { + if (n === 1 || n === 2) return n; + // 部分問題の解を保存するために dp テーブルを初期化 + const dp = new Array(n + 1).fill(-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]; +} + +/* 階段登り:空間最適化した動的計画法 */ +function climbingStairsDPComp(n: number): number { + if (n === 1 || n === 2) return n; + let a = 1, + b = 2; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* Driver Code */ +const n = 9; +let res = climbingStairsDP(n); +console.log(`${n} 段の階段を登る方法は ${res} 通り`); +res = climbingStairsDPComp(n); +console.log(`${n} 段の階段を登る方法は ${res} 通り`); + +export {}; diff --git a/ja/codes/typescript/chapter_dynamic_programming/coin_change.ts b/ja/codes/typescript/chapter_dynamic_programming/coin_change.ts new file mode 100644 index 000000000..eaee4ce62 --- /dev/null +++ b/ja/codes/typescript/chapter_dynamic_programming/coin_change.ts @@ -0,0 +1,68 @@ +/** + * File: coin_change.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* コイン両替:動的計画法 */ +function coinChangeDP(coins: Array, amt: number): number { + const n = coins.length; + const MAX = amt + 1; + // dp テーブルを初期化 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // 状態遷移:先頭行と先頭列 + for (let a = 1; a <= amt; a++) { + dp[0][a] = MAX; + } + // 状態遷移: 残りの行と列 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[i][a] = dp[i - 1][a]; + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[i][a] = Math.min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1); + } + } + } + return dp[n][amt] !== MAX ? dp[n][amt] : -1; +} + +/* コイン交換:空間最適化後の動的計画法 */ +function coinChangeDPComp(coins: Array, amt: number): number { + const n = coins.length; + const MAX = amt + 1; + // dp テーブルを初期化 + const dp = Array.from({ length: amt + 1 }, () => MAX); + dp[0] = 0; + // 状態遷移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a]; + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[a] = Math.min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] !== MAX ? dp[amt] : -1; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 4; + +// 動的計画法 +let res = coinChangeDP(coins, amt); +console.log(`目標金額を作るのに必要な最小コイン枚数は ${res}`); + +// 空間最適化後の動的計画法 +res = coinChangeDPComp(coins, amt); +console.log(`目標金額を作るのに必要な最小コイン枚数は ${res}`); + +export {}; diff --git a/ja/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts b/ja/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts new file mode 100644 index 000000000..30bbbd623 --- /dev/null +++ b/ja/codes/typescript/chapter_dynamic_programming/coin_change_ii.ts @@ -0,0 +1,66 @@ +/** + * File: coin_change_ii.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* コイン両替 II:動的計画法 */ +function coinChangeIIDP(coins: Array, amt: number): number { + const n = coins.length; + // dp テーブルを初期化 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: amt + 1 }, () => 0) + ); + // 先頭列を初期化する + for (let i = 0; i <= n; i++) { + dp[i][0] = 1; + } + // 状態遷移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[i][a] = dp[i - 1][a]; + } else { + // コイン i を選ばない場合と選ぶ場合の和 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; +} + +/* コイン両替 II:空間最適化した動的計画法 */ +function coinChangeIIDPComp(coins: Array, amt: number): number { + const n = coins.length; + // dp テーブルを初期化 + const dp = Array.from({ length: amt + 1 }, () => 0); + dp[0] = 1; + // 状態遷移 + for (let i = 1; i <= n; i++) { + for (let a = 1; a <= amt; a++) { + if (coins[i - 1] > a) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a]; + } else { + // コイン i を選ばない場合と選ぶ場合の和 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; +} + +/* Driver Code */ +const coins = [1, 2, 5]; +const amt = 5; + +// 動的計画法 +let res = coinChangeIIDP(coins, amt); +console.log(`目標金額を作るコインの組み合わせ数は ${res}`); + +// 空間最適化後の動的計画法 +res = coinChangeIIDPComp(coins, amt); +console.log(`目標金額を作るコインの組み合わせ数は ${res}`); + +export {}; diff --git a/ja/codes/typescript/chapter_dynamic_programming/edit_distance.ts b/ja/codes/typescript/chapter_dynamic_programming/edit_distance.ts new file mode 100644 index 000000000..b00f46543 --- /dev/null +++ b/ja/codes/typescript/chapter_dynamic_programming/edit_distance.ts @@ -0,0 +1,148 @@ +/** + * File: edit_distance.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 編集距離:総当たり探索 */ +function editDistanceDFS(s: string, t: string, i: number, j: number): number { + // s と t がともに空なら 0 を返す + if (i === 0 && j === 0) return 0; + + // s が空なら t の長さを返す + if (i === 0) return j; + + // t が空なら s の長さを返す + if (j === 0) return i; + + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if (s.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFS(s, t, i - 1, j - 1); + + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + const insert = editDistanceDFS(s, t, i, j - 1); + const del = editDistanceDFS(s, t, i - 1, j); + const replace = editDistanceDFS(s, t, i - 1, j - 1); + // 最小編集回数を返す + return Math.min(insert, del, replace) + 1; +} + +/* 編集距離:メモ化探索 */ +function editDistanceDFSMem( + s: string, + t: string, + mem: Array>, + i: number, + j: number +): number { + // s と t がともに空なら 0 を返す + if (i === 0 && j === 0) return 0; + + // s が空なら t の長さを返す + if (i === 0) return j; + + // t が空なら s の長さを返す + if (j === 0) return i; + + // 記録済みなら、それをそのまま返す + if (mem[i][j] !== -1) return mem[i][j]; + + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if (s.charAt(i - 1) === t.charAt(j - 1)) + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + const insert = editDistanceDFSMem(s, t, mem, i, j - 1); + const del = editDistanceDFSMem(s, t, mem, i - 1, j); + const replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 最小編集回数を記録して返す + mem[i][j] = Math.min(insert, del, replace) + 1; + return mem[i][j]; +} + +/* 編集距離:動的計画法 */ +function editDistanceDP(s: string, t: string): number { + const n = s.length, + m = t.length; + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: m + 1 }, () => 0) + ); + // 状態遷移:先頭行と先頭列 + for (let i = 1; i <= n; i++) { + dp[i][0] = i; + } + for (let j = 1; j <= m; j++) { + dp[0][j] = j; + } + // 状態遷移: 残りの行と列 + for (let i = 1; i <= n; i++) { + for (let j = 1; j <= m; j++) { + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[i][j] = + Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +/* 編集距離:空間最適化した動的計画法 */ +function editDistanceDPComp(s: string, t: string): number { + const n = s.length, + m = t.length; + const dp = new Array(m + 1).fill(0); + // 状態遷移:先頭行 + for (let j = 1; j <= m; j++) { + dp[j] = j; + } + // 状態遷移:残りの行 + for (let i = 1; i <= n; i++) { + // 状態遷移:先頭列 + let leftup = dp[0]; // dp[i-1, j-1] を一時保存する + dp[0] = i; + // 状態遷移:残りの列 + for (let j = 1; j <= m; j++) { + const temp = dp[j]; + if (s.charAt(i - 1) === t.charAt(j - 1)) { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[j] = leftup; + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[j] = Math.min(dp[j - 1], dp[j], leftup) + 1; + } + leftup = temp; // 次の反復の dp[i-1, j-1] に更新する + } + } + return dp[m]; +} + +/* Driver Code */ +const s = 'bag'; +const t = 'pack'; +const n = s.length, + m = t.length; + +// 全探索 +let res = editDistanceDFS(s, t, n, m); +console.log(`${s} を ${t} に変更するには最小で ${res} 回の編集が必要`); + +// メモ化探索 +const mem = Array.from({ length: n + 1 }, () => + Array.from({ length: m + 1 }, () => -1) +); +res = editDistanceDFSMem(s, t, mem, n, m); +console.log(`${s} を ${t} に変更するには最小で ${res} 回の編集が必要`); + +// 動的計画法 +res = editDistanceDP(s, t); +console.log(`${s} を ${t} に変更するには最小で ${res} 回の編集が必要`); + +// 空間最適化後の動的計画法 +res = editDistanceDPComp(s, t); +console.log(`${s} を ${t} に変更するには最小で ${res} 回の編集が必要`); + +export {}; diff --git a/ja/codes/typescript/chapter_dynamic_programming/knapsack.ts b/ja/codes/typescript/chapter_dynamic_programming/knapsack.ts new file mode 100644 index 000000000..54bbfab51 --- /dev/null +++ b/ja/codes/typescript/chapter_dynamic_programming/knapsack.ts @@ -0,0 +1,134 @@ +/** + * File: knapsack.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 0-1 ナップサック:総当たり探索 */ +function knapsackDFS( + wgt: Array, + val: Array, + i: number, + c: number +): number { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if (i === 0 || c === 0) { + return 0; + } + // ナップサック容量を超える場合は、入れない選択しかできない + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + const no = knapsackDFS(wgt, val, i - 1, c); + const yes = knapsackDFS(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 2つの案のうち価値が大きいほうを返す + return Math.max(no, yes); +} + +/* 0-1 ナップサック:メモ化探索 */ +function knapsackDFSMem( + wgt: Array, + val: Array, + mem: Array>, + i: number, + c: number +): number { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if (i === 0 || c === 0) { + return 0; + } + // 既に記録があればそのまま返す + if (mem[i][c] !== -1) { + return mem[i][c]; + } + // ナップサック容量を超える場合は、入れない選択しかできない + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + const no = knapsackDFSMem(wgt, val, mem, i - 1, c); + const yes = + knapsackDFSMem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1]; + // 2 つの案のうち価値が大きい方を記録して返す + mem[i][c] = Math.max(no, yes); + return mem[i][c]; +} + +/* 0-1 ナップサック:動的計画法 */ +function knapsackDP( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // dp テーブルを初期化 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => 0) + ); + // 状態遷移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[i][c] = dp[i - 1][c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i - 1][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* 0-1 ナップサック:空間最適化後の動的計画法 */ +function knapsackDPComp( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // dp テーブルを初期化 + const dp = Array(cap + 1).fill(0); + // 状態遷移 + for (let i = 1; i <= n; i++) { + // 逆順に走査する + for (let c = cap; c >= 1; c--) { + if (wgt[i - 1] <= c) { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [10, 20, 30, 40, 50]; +const val = [50, 120, 150, 210, 240]; +const cap = 50; +const n = wgt.length; + +// 全探索 +let res = knapsackDFS(wgt, val, n, cap); +console.log(`ナップサック容量を超えない最大価値は ${res}`); + +// メモ化探索 +const mem = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => -1) +); +res = knapsackDFSMem(wgt, val, mem, n, cap); +console.log(`ナップサック容量を超えない最大価値は ${res}`); + +// 動的計画法 +res = knapsackDP(wgt, val, cap); +console.log(`ナップサック容量を超えない最大価値は ${res}`); + +// 空間最適化後の動的計画法 +res = knapsackDPComp(wgt, val, cap); +console.log(`ナップサック容量を超えない最大価値は ${res}`); + +export {}; diff --git a/ja/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts b/ja/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts new file mode 100644 index 000000000..fa8da1e5f --- /dev/null +++ b/ja/codes/typescript/chapter_dynamic_programming/min_cost_climbing_stairs_dp.ts @@ -0,0 +1,51 @@ +/** + * File: min_cost_climbing_stairs_dp.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 階段登りの最小コスト:動的計画法 */ +function minCostClimbingStairsDP(cost: Array): number { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + // 部分問題の解を保存するために dp テーブルを初期化 + const dp = new Array(n + 1); + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for (let i = 3; i <= n; i++) { + dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +/* 階段昇りの最小コスト:空間最適化後の動的計画法 */ +function minCostClimbingStairsDPComp(cost: Array): number { + const n = cost.length - 1; + if (n === 1 || n === 2) { + return cost[n]; + } + let a = cost[1], + b = cost[2]; + for (let i = 3; i <= n; i++) { + const tmp = b; + b = Math.min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +/* Driver Code */ +const cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1]; +console.log(`入力された階段のコスト一覧:${cost}`); + +let res = minCostClimbingStairsDP(cost); +console.log(`階段を登り切るための最小コスト:${res}`); + +res = minCostClimbingStairsDPComp(cost); +console.log(`階段を登り切るための最小コスト:${res}`); + +export {}; diff --git a/ja/codes/typescript/chapter_dynamic_programming/min_path_sum.ts b/ja/codes/typescript/chapter_dynamic_programming/min_path_sum.ts new file mode 100644 index 000000000..33df902d5 --- /dev/null +++ b/ja/codes/typescript/chapter_dynamic_programming/min_path_sum.ts @@ -0,0 +1,132 @@ +/** + * File: min_path_sum.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 最小経路和:全探索 */ +function minPathSumDFS( + grid: Array>, + i: number, + j: number +): number { + // 左上のセルなら探索を終了する + if (i === 0 && j == 0) { + return grid[0][0]; + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if (i < 0 || j < 0) { + return Infinity; + } + // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する + const up = minPathSumDFS(grid, i - 1, j); + const left = minPathSumDFS(grid, i, j - 1); + // 左上隅から (i, j) までの最小経路コストを返す + return Math.min(left, up) + grid[i][j]; +} + +/* 最小経路和:メモ化探索 */ +function minPathSumDFSMem( + grid: Array>, + mem: Array>, + i: number, + j: number +): number { + // 左上のセルなら探索を終了する + if (i === 0 && j === 0) { + return grid[0][0]; + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if (i < 0 || j < 0) { + return Infinity; + } + // 既に記録があればそのまま返す + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 左と上のセルからの最小経路コスト + const up = minPathSumDFSMem(grid, mem, i - 1, j); + const left = minPathSumDFSMem(grid, mem, i, j - 1); + // 左上から (i, j) までの最小経路コストを記録して返す + mem[i][j] = Math.min(left, up) + grid[i][j]; + return mem[i][j]; +} + +/* 最小経路和:動的計画法 */ +function minPathSumDP(grid: Array>): number { + const n = grid.length, + m = grid[0].length; + // dp テーブルを初期化 + const dp = Array.from({ length: n }, () => + Array.from({ length: m }, () => 0) + ); + dp[0][0] = grid[0][0]; + // 状態遷移:先頭行 + for (let j = 1; j < m; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 状態遷移:先頭列 + for (let i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 状態遷移: 残りの行と列 + for (let i = 1; i < n; i++) { + for (let j: number = 1; j < m; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +/* 最小経路和:空間最適化後の動的計画法 */ +function minPathSumDPComp(grid: Array>): number { + const n = grid.length, + m = grid[0].length; + // dp テーブルを初期化 + const dp = new Array(m); + // 状態遷移:先頭行 + dp[0] = grid[0][0]; + for (let j = 1; j < m; j++) { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状態遷移:残りの行 + for (let i = 1; i < n; i++) { + // 状態遷移:先頭列 + dp[0] = dp[0] + grid[i][0]; + // 状態遷移:残りの列 + for (let j = 1; j < m; j++) { + dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +/* Driver Code */ +const grid = [ + [1, 3, 1, 5], + [2, 2, 4, 2], + [5, 3, 2, 1], + [4, 3, 5, 2], +]; +const n = grid.length, + m = grid[0].length; +// 全探索 +let res = minPathSumDFS(grid, n - 1, m - 1); +console.log(`左上から右下までの最小経路和は ${res}`); + +// メモ化探索 +const mem = Array.from({ length: n }, () => + Array.from({ length: m }, () => -1) +); +res = minPathSumDFSMem(grid, mem, n - 1, m - 1); +console.log(`左上から右下までの最小経路和は ${res}`); + +// 動的計画法 +res = minPathSumDP(grid); +console.log(`左上から右下までの最小経路和は ${res}`); + +// 空間最適化後の動的計画法 +res = minPathSumDPComp(grid); +console.log(`左上から右下までの最小経路和は ${res}`); + +export {}; diff --git a/ja/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts b/ja/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts new file mode 100644 index 000000000..a2820997f --- /dev/null +++ b/ja/codes/typescript/chapter_dynamic_programming/unbounded_knapsack.ts @@ -0,0 +1,73 @@ +/** + * File: unbounded_knapsack.ts + * Created Time: 2023-08-23 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 完全ナップサック問題:動的計画法 */ +function unboundedKnapsackDP( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // dp テーブルを初期化 + const dp = Array.from({ length: n + 1 }, () => + Array.from({ length: cap + 1 }, () => 0) + ); + // 状態遷移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[i][c] = dp[i - 1][c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i][c] = Math.max( + dp[i - 1][c], + dp[i][c - wgt[i - 1]] + val[i - 1] + ); + } + } + } + return dp[n][cap]; +} + +/* 完全ナップサック問題:空間最適化後の動的計画法 */ +function unboundedKnapsackDPComp( + wgt: Array, + val: Array, + cap: number +): number { + const n = wgt.length; + // dp テーブルを初期化 + const dp = Array.from({ length: cap + 1 }, () => 0); + // 状態遷移 + for (let i = 1; i <= n; i++) { + for (let c = 1; c <= cap; c++) { + if (wgt[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[c] = dp[c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = Math.max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* Driver Code */ +const wgt = [1, 2, 3]; +const val = [5, 11, 15]; +const cap = 4; + +// 動的計画法 +let res = unboundedKnapsackDP(wgt, val, cap); +console.log(`ナップサック容量を超えない最大価値は ${res}`); + +// 空間最適化後の動的計画法 +res = unboundedKnapsackDPComp(wgt, val, cap); +console.log(`ナップサック容量を超えない最大価値は ${res}`); + +export {}; diff --git a/ja/codes/typescript/chapter_graph/graph_adjacency_list.ts b/ja/codes/typescript/chapter_graph/graph_adjacency_list.ts new file mode 100644 index 000000000..6260398c6 --- /dev/null +++ b/ja/codes/typescript/chapter_graph/graph_adjacency_list.ts @@ -0,0 +1,140 @@ +/** + * File: graph_adjacency_list.ts + * Created Time: 2023-02-09 + * Author: Justin (xiefahit@gmail.com) + */ + +import { Vertex } from '../modules/Vertex'; + +/* 隣接リストに基づく無向グラフクラス */ +class GraphAdjList { + // 隣接リスト。key は頂点、value はその頂点に隣接する全頂点 + adjList: Map; + + /* コンストラクタ */ + constructor(edges: Vertex[][]) { + this.adjList = new Map(); + // すべての頂点と辺を追加 + for (const edge of edges) { + this.addVertex(edge[0]); + this.addVertex(edge[1]); + this.addEdge(edge[0], edge[1]); + } + } + + /* 頂点数を取得 */ + size(): number { + return this.adjList.size; + } + + /* 辺を追加 */ + addEdge(vet1: Vertex, vet2: Vertex): void { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 + ) { + throw new Error('Illegal Argument Exception'); + } + // 辺 vet1 - vet2 を追加 + this.adjList.get(vet1).push(vet2); + this.adjList.get(vet2).push(vet1); + } + + /* 辺を削除 */ + removeEdge(vet1: Vertex, vet2: Vertex): void { + if ( + !this.adjList.has(vet1) || + !this.adjList.has(vet2) || + vet1 === vet2 || + this.adjList.get(vet1).indexOf(vet2) === -1 + ) { + throw new Error('Illegal Argument Exception'); + } + // 辺 vet1 - vet2 を削除 + this.adjList.get(vet1).splice(this.adjList.get(vet1).indexOf(vet2), 1); + this.adjList.get(vet2).splice(this.adjList.get(vet2).indexOf(vet1), 1); + } + + /* 頂点を追加 */ + addVertex(vet: Vertex): void { + if (this.adjList.has(vet)) return; + // 隣接リストに新しいリストを追加 + this.adjList.set(vet, []); + } + + /* 頂点を削除 */ + removeVertex(vet: Vertex): void { + if (!this.adjList.has(vet)) { + throw new Error('Illegal Argument Exception'); + } + // 隣接リストから頂点 vet に対応するリストを削除 + this.adjList.delete(vet); + // 他の頂点のリストを走査し、vet を含むすべての辺を削除 + for (const set of this.adjList.values()) { + const index: number = set.indexOf(vet); + if (index > -1) { + set.splice(index, 1); + } + } + } + + /* 隣接リストを出力 */ + print(): void { + console.log('隣接リスト ='); + for (const [key, value] of this.adjList.entries()) { + const tmp = []; + for (const vertex of value) { + tmp.push(vertex.val); + } + console.log(key.val + ': ' + tmp.join()); + } + } +} + +/* Driver Code */ +if (import.meta.url.endsWith(process.argv[1])) { + /* 無向グラフを初期化 */ + const v0 = new Vertex(1), + v1 = new Vertex(3), + v2 = new Vertex(2), + v3 = new Vertex(5), + v4 = new Vertex(4); + const edges = [ + [v0, v1], + [v1, v2], + [v2, v3], + [v0, v3], + [v2, v4], + [v3, v4], + ]; + const graph = new GraphAdjList(edges); + console.log('\n初期化後、グラフは'); + graph.print(); + + /* 辺を追加 */ + // 頂点 1, 2 は v0, v2 + graph.addEdge(v0, v2); + console.log('\n辺 1-2 を追加した後、グラフは'); + graph.print(); + + /* 辺を削除 */ + // 頂点 1, 3 は v0, v1 + graph.removeEdge(v0, v1); + console.log('\n辺 1-3 を削除した後、グラフは'); + graph.print(); + + /* 頂点を追加 */ + const v5 = new Vertex(6); + graph.addVertex(v5); + console.log('\n頂点 6 を追加した後、グラフは'); + graph.print(); + + /* 頂点を削除 */ + // 頂点 3 は v1 + graph.removeVertex(v1); + console.log('\n頂点 3 を削除した後、グラフは'); + graph.print(); +} + +export { GraphAdjList }; diff --git a/ja/codes/typescript/chapter_graph/graph_adjacency_matrix.ts b/ja/codes/typescript/chapter_graph/graph_adjacency_matrix.ts new file mode 100644 index 000000000..70ec91c0e --- /dev/null +++ b/ja/codes/typescript/chapter_graph/graph_adjacency_matrix.ts @@ -0,0 +1,134 @@ +/** + * File: graph_adjacency_matrix.ts + * Created Time: 2023-02-09 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 隣接行列に基づく無向グラフクラス */ +class GraphAdjMat { + vertices: number[]; // 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す + adjMat: number[][]; // 隣接行列。行・列のインデックスは「頂点インデックス」に対応 + + /* コンストラクタ */ + constructor(vertices: number[], edges: number[][]) { + this.vertices = []; + this.adjMat = []; + // 頂点を追加 + for (const val of vertices) { + this.addVertex(val); + } + // 辺を追加 + // 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する + for (const e of edges) { + this.addEdge(e[0], e[1]); + } + } + + /* 頂点数を取得 */ + size(): number { + return this.vertices.length; + } + + /* 頂点を追加 */ + addVertex(val: number): void { + const n: number = this.size(); + // 頂点リストに新しい頂点の値を追加 + this.vertices.push(val); + // 隣接行列に 1 行追加 + const newRow: number[] = []; + for (let j: number = 0; j < n; j++) { + newRow.push(0); + } + this.adjMat.push(newRow); + // 隣接行列に 1 列追加 + for (const row of this.adjMat) { + row.push(0); + } + } + + /* 頂点を削除 */ + removeVertex(index: number): void { + if (index >= this.size()) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // 頂点リストから index の頂点を削除する + this.vertices.splice(index, 1); + + // 隣接行列で index 行を削除する + this.adjMat.splice(index, 1); + // 隣接行列で index 列を削除する + for (const row of this.adjMat) { + row.splice(index, 1); + } + } + + /* 辺を追加 */ + // 引数 i, j は vertices の要素インデックスに対応する + addEdge(i: number, j: number): void { + // インデックスの範囲外と等値の処理 + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + // 無向グラフでは、隣接行列は主対角線に関して対称であり、(i, j) === (j, i) を満たす + this.adjMat[i][j] = 1; + this.adjMat[j][i] = 1; + } + + /* 辺を削除 */ + // 引数 i, j は vertices の要素インデックスに対応する + removeEdge(i: number, j: number): void { + // インデックスの範囲外と等値の処理 + if (i < 0 || j < 0 || i >= this.size() || j >= this.size() || i === j) { + throw new RangeError('Index Out Of Bounds Exception'); + } + this.adjMat[i][j] = 0; + this.adjMat[j][i] = 0; + } + + /* 隣接行列を出力 */ + print(): void { + console.log('頂点リスト = ', this.vertices); + console.log('隣接行列 =', this.adjMat); + } +} + +/* Driver Code */ +/* 無向グラフを初期化 */ +// edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意 +const vertices: number[] = [1, 3, 2, 5, 4]; +const edges: number[][] = [ + [0, 1], + [1, 2], + [2, 3], + [0, 3], + [2, 4], + [3, 4], +]; +const graph: GraphAdjMat = new GraphAdjMat(vertices, edges); +console.log('\n初期化後、グラフは'); +graph.print(); + +/* 辺を追加 */ +// 頂点 1, 2 のインデックスはそれぞれ 0, 2 +graph.addEdge(0, 2); +console.log('\n辺 1-2 を追加した後、グラフは'); +graph.print(); + +/* 辺を削除 */ +// 頂点 1, 3 のインデックスはそれぞれ 0, 1 +graph.removeEdge(0, 1); +console.log('\n辺 1-3 を削除した後、グラフは'); +graph.print(); + +/* 頂点を追加 */ +graph.addVertex(6); +console.log('\n頂点 6 を追加した後、グラフは'); +graph.print(); + +/* 頂点を削除 */ +// 頂点 3 のインデックスは 1 +graph.removeVertex(1); +console.log('\n頂点 3 を削除した後、グラフは'); +graph.print(); + +export {}; diff --git a/ja/codes/typescript/chapter_graph/graph_bfs.ts b/ja/codes/typescript/chapter_graph/graph_bfs.ts new file mode 100644 index 000000000..5f3223b07 --- /dev/null +++ b/ja/codes/typescript/chapter_graph/graph_bfs.ts @@ -0,0 +1,61 @@ +/** + * File: graph_bfs.ts + * Created Time: 2023-02-21 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +import { GraphAdjList } from './graph_adjacency_list'; +import { Vertex } from '../modules/Vertex'; + +/* 幅優先探索 */ +// グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする +function graphBFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { + // 頂点の走査順序 + const res: Vertex[] = []; + // 訪問済み頂点を記録するためのハッシュ集合 + const visited: Set = new Set(); + visited.add(startVet); + // BFS の実装にキューを用いる + const que = [startVet]; + // 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す + while (que.length) { + const vet = que.shift(); // 先頭の頂点をデキュー + res.push(vet); // 訪問した頂点を記録 + // この頂点のすべての隣接頂点を走査 + for (const adjVet of graph.adjList.get(vet) ?? []) { + if (visited.has(adjVet)) { + continue; // 訪問済みの頂点をスキップ + } + que.push(adjVet); // 未訪問のものだけをキューに入れる + visited.add(adjVet); // この頂点を訪問済みにする + } + } + // 頂点の走査順を返す + return res; +} + +/* Driver Code */ +/* 無向グラフを初期化 */ +const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); +const edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[1], v[4]], + [v[2], v[5]], + [v[3], v[4]], + [v[3], v[6]], + [v[4], v[5]], + [v[4], v[7]], + [v[5], v[8]], + [v[6], v[7]], + [v[7], v[8]], +]; +const graph = new GraphAdjList(edges); +console.log('\n初期化後、グラフは'); +graph.print(); + +/* 幅優先探索 */ +const res = graphBFS(graph, v[0]); +console.log('\n幅優先探索(BFS)の頂点列は'); +console.log(Vertex.vetsToVals(res)); diff --git a/ja/codes/typescript/chapter_graph/graph_dfs.ts b/ja/codes/typescript/chapter_graph/graph_dfs.ts new file mode 100644 index 000000000..bbaa642ac --- /dev/null +++ b/ja/codes/typescript/chapter_graph/graph_dfs.ts @@ -0,0 +1,58 @@ +/** + * File: graph_dfs.ts + * Created Time: 2023-02-21 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +import { Vertex } from '../modules/Vertex'; +import { GraphAdjList } from './graph_adjacency_list'; + +/* 深さ優先走査の補助関数 */ +function dfs( + graph: GraphAdjList, + visited: Set, + res: Vertex[], + vet: Vertex +): void { + res.push(vet); // 訪問した頂点を記録 + visited.add(vet); // この頂点を訪問済みにする + // この頂点のすべての隣接頂点を走査 + for (const adjVet of graph.adjList.get(vet)) { + if (visited.has(adjVet)) { + continue; // 訪問済みの頂点をスキップ + } + // 隣接頂点を再帰的に訪問 + dfs(graph, visited, res, adjVet); + } +} + +/* 深さ優先探索 */ +// グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする +function graphDFS(graph: GraphAdjList, startVet: Vertex): Vertex[] { + // 頂点の走査順序 + const res: Vertex[] = []; + // 訪問済み頂点を記録するためのハッシュ集合 + const visited: Set = new Set(); + dfs(graph, visited, res, startVet); + return res; +} + +/* Driver Code */ +/* 無向グラフを初期化 */ +const v = Vertex.valsToVets([0, 1, 2, 3, 4, 5, 6]); +const edges = [ + [v[0], v[1]], + [v[0], v[3]], + [v[1], v[2]], + [v[2], v[5]], + [v[4], v[5]], + [v[5], v[6]], +]; +const graph = new GraphAdjList(edges); +console.log('\n初期化後、グラフは'); +graph.print(); + +/* 深さ優先探索 */ +const res = graphDFS(graph, v[0]); +console.log('\n深さ優先探索(DFS)の頂点列は'); +console.log(Vertex.vetsToVals(res)); diff --git a/ja/codes/typescript/chapter_greedy/coin_change_greedy.ts b/ja/codes/typescript/chapter_greedy/coin_change_greedy.ts new file mode 100644 index 000000000..813cb4015 --- /dev/null +++ b/ja/codes/typescript/chapter_greedy/coin_change_greedy.ts @@ -0,0 +1,50 @@ +/** + * File: coin_change_greedy.ts + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* コイン交換:貪欲法 */ +function coinChangeGreedy(coins: number[], amt: number): number { + // coins 配列はソート済みと仮定する + let i = coins.length - 1; + let count = 0; + // 残額がなくなるまで貪欲選択を繰り返す + while (amt > 0) { + // 残額以下で最も近い硬貨を見つける + while (i > 0 && coins[i] > amt) { + i--; + } + // coins[i] を選択する + amt -= coins[i]; + count++; + } + // 実行可能な解が見つからなければ -1 を返す + return amt === 0 ? count : -1; +} + +/* Driver Code */ +// 貪欲法:大域最適解を保証できる +let coins: number[] = [1, 5, 10, 20, 50, 100]; +let amt: number = 186; +let res: number = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`金額 ${amt} を作るのに必要な最小硬貨枚数は ${res}`); + +// 貪欲法:大域最適解を保証できない +coins = [1, 20, 50]; +amt = 60; +res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`金額 ${amt} を作るのに必要な最小硬貨枚数は ${res}`); +console.log('実際に必要な最小枚数は 3 ,つまり 20 + 20 + 20'); + +// 貪欲法:大域最適解を保証できない +coins = [1, 49, 50]; +amt = 98; +res = coinChangeGreedy(coins, amt); +console.log(`\ncoins = ${coins}, amt = ${amt}`); +console.log(`金額 ${amt} を作るのに必要な最小硬貨枚数は ${res}`); +console.log('実際に必要な最小枚数は 2 ,つまり 49 + 49'); + +export {}; diff --git a/ja/codes/typescript/chapter_greedy/fractional_knapsack.ts b/ja/codes/typescript/chapter_greedy/fractional_knapsack.ts new file mode 100644 index 000000000..fa23b9bb0 --- /dev/null +++ b/ja/codes/typescript/chapter_greedy/fractional_knapsack.ts @@ -0,0 +1,50 @@ +/** + * File: fractional_knapsack.ts + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 品物 */ +class Item { + w: number; // 品物の重さ + v: number; // 品物の価値 + + constructor(w: number, v: number) { + this.w = w; + this.v = v; + } +} + +/* 分数ナップサック:貪欲法 */ +function fractionalKnapsack(wgt: number[], val: number[], cap: number): number { + // 重さと価値の 2 属性を持つ品物リストを作成 + const items: Item[] = wgt.map((w, i) => new Item(w, val[i])); + // 単位価値 item.v / item.w の高い順にソートする + items.sort((a, b) => b.v / b.w - a.v / a.w); + // 貪欲選択を繰り返す + let res = 0; + for (const item of items) { + if (item.w <= cap) { + // 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる + res += item.v; + cap -= item.w; + } else { + // 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる + res += (item.v / item.w) * cap; + // 残り容量がないため、ループを抜ける + break; + } + } + return res; +} + +/* Driver Code */ +const wgt: number[] = [10, 20, 30, 40, 50]; +const val: number[] = [50, 120, 150, 210, 240]; +const cap: number = 50; + +// 貪欲法 +const res: number = fractionalKnapsack(wgt, val, cap); +console.log(`ナップサック容量を超えない最大価値は ${res}`); + +export {}; diff --git a/ja/codes/typescript/chapter_greedy/max_capacity.ts b/ja/codes/typescript/chapter_greedy/max_capacity.ts new file mode 100644 index 000000000..5b2dfac9c --- /dev/null +++ b/ja/codes/typescript/chapter_greedy/max_capacity.ts @@ -0,0 +1,36 @@ +/** + * File: max_capacity.ts + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 最大容量:貪欲法 */ +function maxCapacity(ht: number[]): number { + // i, j を初期化し、それぞれ配列の両端に置く + let i = 0, + j = ht.length - 1; + // 初期の最大容量は 0 + let res = 0; + // 2 枚の板が出会うまで貪欲選択を繰り返す + while (i < j) { + // 最大容量を更新する + const cap: number = Math.min(ht[i], ht[j]) * (j - i); + res = Math.max(res, cap); + // 短い方を内側へ動かす + if (ht[i] < ht[j]) { + i += 1; + } else { + j -= 1; + } + } + return res; +} + +/* Driver Code */ +const ht: number[] = [3, 8, 5, 2, 7, 7, 3, 4]; + +// 貪欲法 +const res: number = maxCapacity(ht); +console.log(`最大容量は ${res}`); + +export {}; diff --git a/ja/codes/typescript/chapter_greedy/max_product_cutting.ts b/ja/codes/typescript/chapter_greedy/max_product_cutting.ts new file mode 100644 index 000000000..263d258c3 --- /dev/null +++ b/ja/codes/typescript/chapter_greedy/max_product_cutting.ts @@ -0,0 +1,35 @@ +/** + * File: max_product_cutting.ts + * Created Time: 2023-09-02 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 最大切断積:貪欲法 */ +function maxProductCutting(n: number): number { + // n <= 3 のときは、必ず 1 を切り出す + if (n <= 3) { + return 1 * (n - 1); + } + // 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする + let a: number = Math.floor(n / 3); + let b: number = n % 3; + if (b === 1) { + // 余りが 1 のときは、1 * 3 を 2 * 2 に変える + return Math.pow(3, a - 1) * 2 * 2; + } + if (b === 2) { + // 余りが 2 のときは、そのままにする + return Math.pow(3, a) * 2; + } + // 余りが 0 のときは、そのままにする + return Math.pow(3, a); +} + +/* Driver Code */ +let n: number = 58; + +// 貪欲法 +let res: number = maxProductCutting(n); +console.log(`最大分割積は ${res}`); + +export {}; diff --git a/ja/codes/typescript/chapter_hashing/array_hash_map.ts b/ja/codes/typescript/chapter_hashing/array_hash_map.ts new file mode 100644 index 000000000..309f15133 --- /dev/null +++ b/ja/codes/typescript/chapter_hashing/array_hash_map.ts @@ -0,0 +1,134 @@ +/** + * File: array_hash_map.ts + * Created Time: 2022-12-20 + * Author: Daniel (better.sunjian@gmail.com) + */ + +/* キーと値の組 Number -> String */ +class Pair { + public key: number; + public val: string; + + constructor(key: number, val: string) { + this.key = key; + this.val = val; + } +} + +/* 配列ベースのハッシュテーブル */ +class ArrayHashMap { + private readonly buckets: (Pair | null)[]; + + constructor() { + // 100 個のバケットを含む配列を初期化 + this.buckets = new Array(100).fill(null); + } + + /* ハッシュ関数 */ + private hashFunc(key: number): number { + return key % 100; + } + + /* 検索操作 */ + public get(key: number): string | null { + let index = this.hashFunc(key); + let pair = this.buckets[index]; + if (pair === null) return null; + return pair.val; + } + + /* 追加操作 */ + public set(key: number, val: string) { + let index = this.hashFunc(key); + this.buckets[index] = new Pair(key, val); + } + + /* 削除操作 */ + public delete(key: number) { + let index = this.hashFunc(key); + // null に設定し、削除を表す + this.buckets[index] = null; + } + + /* すべてのキーと値のペアを取得 */ + public entries(): (Pair | null)[] { + let arr: (Pair | null)[] = []; + for (let i = 0; i < this.buckets.length; i++) { + if (this.buckets[i]) { + arr.push(this.buckets[i]); + } + } + return arr; + } + + /* すべてのキーを取得 */ + public keys(): (number | undefined)[] { + let arr: (number | undefined)[] = []; + for (let i = 0; i < this.buckets.length; i++) { + if (this.buckets[i]) { + arr.push(this.buckets[i].key); + } + } + return arr; + } + + /* すべての値を取得 */ + public values(): (string | undefined)[] { + let arr: (string | undefined)[] = []; + for (let i = 0; i < this.buckets.length; i++) { + if (this.buckets[i]) { + arr.push(this.buckets[i].val); + } + } + return arr; + } + + /* ハッシュテーブルを出力 */ + public print() { + let pairSet = this.entries(); + for (const pair of pairSet) { + console.info(`${pair.key} -> ${pair.val}`); + } + } +} + +/* Driver Code */ +/* ハッシュテーブルを初期化 */ +const map = new ArrayHashMap(); +/* 追加操作 */ +// ハッシュテーブルにキーと値のペア (key, value) を追加 +map.set(12836, 'シャオハー'); +map.set(15937, 'シャオルオ'); +map.set(16750, 'シャオスワン'); +map.set(13276, 'シャオファー'); +map.set(10583, 'シャオヤー'); +console.info('\n追加完了後,ハッシュテーブルは\nKey -> Value'); +map.print(); + +/* 検索操作 */ +// キー key をハッシュテーブルに渡し、値 value を取得 +let name = map.get(15937); +console.info('\n学籍番号 15937 を入力し,見つかった氏名 ' + name); + +/* 削除操作 */ +// ハッシュテーブルからキーと値のペア (key, value) を削除 +map.delete(10583); +console.info('\n10583 を削除した後,ハッシュテーブルは\nKey -> Value'); +map.print(); + +/* ハッシュテーブルを走査 */ +console.info('\nキーと値の組 Key->Value を走査'); +for (const pair of map.entries()) { + if (!pair) continue; + console.info(pair.key + ' -> ' + pair.val); +} +console.info('\nキー Key を個別に走査'); +for (const key of map.keys()) { + console.info(key); +} +console.info('\n値 Value を個別に走査'); +for (const val of map.values()) { + console.info(val); +} + +export {}; diff --git a/ja/codes/typescript/chapter_hashing/hash_map.ts b/ja/codes/typescript/chapter_hashing/hash_map.ts new file mode 100644 index 000000000..8cf2042f3 --- /dev/null +++ b/ja/codes/typescript/chapter_hashing/hash_map.ts @@ -0,0 +1,46 @@ +/** + * File: hash_map.ts + * Created Time: 2022-12-20 + * Author: Daniel (better.sunjian@gmail.com) + */ + +/* Driver Code */ +/* ハッシュテーブルを初期化 */ +const map = new Map(); + +/* 追加操作 */ +// ハッシュテーブルにキーと値のペア (key, value) を追加 +map.set(12836, 'シャオハー'); +map.set(15937, 'シャオルオ'); +map.set(16750, 'シャオスワン'); +map.set(13276, 'シャオファー'); +map.set(10583, 'シャオヤー'); +console.info('\n追加完了後,ハッシュテーブルは\nKey -> Value'); +console.info(map); + +/* 検索操作 */ +// キー key をハッシュテーブルに渡し、値 value を取得 +let name = map.get(15937); +console.info('\n学籍番号 15937 を入力し,見つかった氏名 ' + name); + +/* 削除操作 */ +// ハッシュテーブルからキーと値のペア (key, value) を削除 +map.delete(10583); +console.info('\n10583 を削除した後,ハッシュテーブルは\nKey -> Value'); +console.info(map); + +/* ハッシュテーブルを走査 */ +console.info('\nキーと値の組 Key->Value を走査'); +for (const [k, v] of map.entries()) { + console.info(k + ' -> ' + v); +} +console.info('\nキー Key を個別に走査'); +for (const k of map.keys()) { + console.info(k); +} +console.info('\n値 Value を個別に走査'); +for (const v of map.values()) { + console.info(v); +} + +export {}; diff --git a/ja/codes/typescript/chapter_hashing/hash_map_chaining.ts b/ja/codes/typescript/chapter_hashing/hash_map_chaining.ts new file mode 100644 index 000000000..8708cefe1 --- /dev/null +++ b/ja/codes/typescript/chapter_hashing/hash_map_chaining.ts @@ -0,0 +1,146 @@ +/** + * File: hash_map_chaining.ts + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* キーと値の組 Number -> String */ +class Pair { + key: number; + val: string; + constructor(key: number, val: string) { + this.key = key; + this.val = val; + } +} + +/* チェイン法ハッシュテーブル */ +class HashMapChaining { + #size: number; // キーと値のペア数 + #capacity: number; // ハッシュテーブル容量 + #loadThres: number; // リサイズを発動する負荷率のしきい値 + #extendRatio: number; // 拡張倍率 + #buckets: Pair[][]; // バケット配列 + + /* コンストラクタ */ + constructor() { + this.#size = 0; + this.#capacity = 4; + this.#loadThres = 2.0 / 3.0; + this.#extendRatio = 2; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + } + + /* ハッシュ関数 */ + #hashFunc(key: number): number { + return key % this.#capacity; + } + + /* 負荷率 */ + #loadFactor(): number { + return this.#size / this.#capacity; + } + + /* 検索操作 */ + get(key: number): string | null { + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // バケットを走査し、key が見つかれば対応する val を返す + for (const pair of bucket) { + if (pair.key === key) { + return pair.val; + } + } + // key が見つからない場合は null を返す + return null; + } + + /* 追加操作 */ + put(key: number, val: string): void { + // 負荷率がしきい値を超えたら、リサイズを実行 + if (this.#loadFactor() > this.#loadThres) { + this.#extend(); + } + const index = this.#hashFunc(key); + const bucket = this.#buckets[index]; + // バケットを走査し、指定した key が見つかれば対応する val を更新して返す + for (const pair of bucket) { + if (pair.key === key) { + pair.val = val; + return; + } + } + // その key が存在しなければ、キーと値のペアを末尾に追加 + const pair = new Pair(key, val); + bucket.push(pair); + this.#size++; + } + + /* 削除操作 */ + remove(key: number): void { + const index = this.#hashFunc(key); + let bucket = this.#buckets[index]; + // バケットを走査してキーと値のペアを削除 + for (let i = 0; i < bucket.length; i++) { + if (bucket[i].key === key) { + bucket.splice(i, 1); + this.#size--; + break; + } + } + } + + /* ハッシュテーブルを拡張 */ + #extend(): void { + // 元のハッシュテーブルを一時保存 + const bucketsTmp = this.#buckets; + // リサイズ後の新しいハッシュテーブルを初期化 + this.#capacity *= this.#extendRatio; + this.#buckets = new Array(this.#capacity).fill(null).map((x) => []); + this.#size = 0; + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す + for (const bucket of bucketsTmp) { + for (const pair of bucket) { + this.put(pair.key, pair.val); + } + } + } + + /* ハッシュテーブルを出力 */ + print(): void { + for (const bucket of this.#buckets) { + let res = []; + for (const pair of bucket) { + res.push(pair.key + ' -> ' + pair.val); + } + console.log(res); + } + } +} + +/* Driver Code */ +/* ハッシュテーブルを初期化 */ +const map = new HashMapChaining(); + +/* 追加操作 */ +// ハッシュテーブルにキーと値のペア (key, value) を追加 +map.put(12836, 'シャオハー'); +map.put(15937, 'シャオルオ'); +map.put(16750, 'シャオスワン'); +map.put(13276, 'シャオファー'); +map.put(10583, 'シャオヤー'); +console.log('\n追加完了後,ハッシュテーブルは\nKey -> Value'); +map.print(); + +/* 検索操作 */ +// キー key をハッシュテーブルに渡し、値 value を取得 +const name = map.get(13276); +console.log('\n学籍番号 13276 を入力し,見つかった氏名 ' + name); + +/* 削除操作 */ +// ハッシュテーブルからキーと値のペア (key, value) を削除 +map.remove(12836); +console.log('\n12836 を削除した後,ハッシュテーブルは\nKey -> Value'); +map.print(); + +export {}; diff --git a/ja/codes/typescript/chapter_hashing/hash_map_open_addressing.ts b/ja/codes/typescript/chapter_hashing/hash_map_open_addressing.ts new file mode 100644 index 000000000..7106d105f --- /dev/null +++ b/ja/codes/typescript/chapter_hashing/hash_map_open_addressing.ts @@ -0,0 +1,182 @@ +/** + * File: hash_map_open_addressing.ts + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com), krahets (krahets@163.com) + */ + +/* キーと値の組 Number -> String */ +class Pair { + key: number; + val: string; + + constructor(key: number, val: string) { + this.key = key; + this.val = val; + } +} + +/* オープンアドレス法ハッシュテーブル */ +class HashMapOpenAddressing { + private size: number; // キーと値のペア数 + private capacity: number; // ハッシュテーブル容量 + private loadThres: number; // リサイズを発動する負荷率のしきい値 + private extendRatio: number; // 拡張倍率 + private buckets: Array; // バケット配列 + private TOMBSTONE: Pair; // 削除済みマーク + + /* コンストラクタ */ + constructor() { + this.size = 0; // キーと値のペア数 + this.capacity = 4; // ハッシュテーブル容量 + this.loadThres = 2.0 / 3.0; // リサイズを発動する負荷率のしきい値 + this.extendRatio = 2; // 拡張倍率 + this.buckets = Array(this.capacity).fill(null); // バケット配列 + this.TOMBSTONE = new Pair(-1, '-1'); // 削除済みマーク + } + + /* ハッシュ関数 */ + private hashFunc(key: number): number { + return key % this.capacity; + } + + /* 負荷率 */ + private loadFactor(): number { + return this.size / this.capacity; + } + + /* key に対応するバケットインデックスを探す */ + private findBucket(key: number): number { + let index = this.hashFunc(key); + let firstTombstone = -1; + // 線形プロービングを行い、空バケットに達したら終了 + while (this.buckets[index] !== null) { + // key が見つかったら、対応するバケットのインデックスを返す + if (this.buckets[index]!.key === key) { + // 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動 + if (firstTombstone !== -1) { + this.buckets[firstTombstone] = this.buckets[index]; + this.buckets[index] = this.TOMBSTONE; + return firstTombstone; // 移動後のバケットインデックスを返す + } + return index; // バケットのインデックスを返す + } + // 最初に見つかった削除マークを記録 + if ( + firstTombstone === -1 && + this.buckets[index] === this.TOMBSTONE + ) { + firstTombstone = index; + } + // バケットのインデックスを計算し、末尾を越えたら先頭に戻る + index = (index + 1) % this.capacity; + } + // key が存在しない場合は追加位置のインデックスを返す + return firstTombstone === -1 ? index : firstTombstone; + } + + /* 検索操作 */ + get(key: number): string | null { + // key に対応するバケットインデックスを探す + const index = this.findBucket(key); + // キーと値の組が見つかったら、対応する val を返す + if ( + this.buckets[index] !== null && + this.buckets[index] !== this.TOMBSTONE + ) { + return this.buckets[index]!.val; + } + // キーと値の組が存在しなければ null を返す + return null; + } + + /* 追加操作 */ + put(key: number, val: string): void { + // 負荷率がしきい値を超えたら、リサイズを実行 + if (this.loadFactor() > this.loadThres) { + this.extend(); + } + // key に対応するバケットインデックスを探す + const index = this.findBucket(key); + // キーと値の組が見つかったら、val を上書きして返す + if ( + this.buckets[index] !== null && + this.buckets[index] !== this.TOMBSTONE + ) { + this.buckets[index]!.val = val; + return; + } + // キーと値の組が存在しない場合は、その組を追加する + this.buckets[index] = new Pair(key, val); + this.size++; + } + + /* 削除操作 */ + remove(key: number): void { + // key に対応するバケットインデックスを探す + const index = this.findBucket(key); + // キーと値の組が見つかったら、削除マーカーで上書きする + if ( + this.buckets[index] !== null && + this.buckets[index] !== this.TOMBSTONE + ) { + this.buckets[index] = this.TOMBSTONE; + this.size--; + } + } + + /* ハッシュテーブルを拡張 */ + private extend(): void { + // 元のハッシュテーブルを一時保存 + const bucketsTmp = this.buckets; + // リサイズ後の新しいハッシュテーブルを初期化 + this.capacity *= this.extendRatio; + this.buckets = Array(this.capacity).fill(null); + this.size = 0; + // キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す + for (const pair of bucketsTmp) { + if (pair !== null && pair !== this.TOMBSTONE) { + this.put(pair.key, pair.val); + } + } + } + + /* ハッシュテーブルを出力 */ + print(): void { + for (const pair of this.buckets) { + if (pair === null) { + console.log('null'); + } else if (pair === this.TOMBSTONE) { + console.log('TOMBSTONE'); + } else { + console.log(pair.key + ' -> ' + pair.val); + } + } + } +} + +/* Driver Code */ +// ハッシュテーブルを初期化 +const hashmap = new HashMapOpenAddressing(); + +// 追加操作 +// ハッシュテーブルにキーと値の組 (key, val) を追加する +hashmap.put(12836, 'シャオハー'); +hashmap.put(15937, 'シャオルオ'); +hashmap.put(16750, 'シャオスワン'); +hashmap.put(13276, 'シャオファー'); +hashmap.put(10583, 'シャオヤー'); +console.log('\n追加完了後,ハッシュテーブルは\nKey -> Value'); +hashmap.print(); + +// 検索操作 +// ハッシュテーブルにキー key を入力し、値 val を得る +const name = hashmap.get(13276); +console.log('\n学籍番号 13276 を入力し,見つかった氏名 ' + name); + +// 削除操作 +// ハッシュテーブルからキーと値の組 (key, val) を削除する +hashmap.remove(16750); +console.log('\n16750 を削除した後,ハッシュテーブルは\nKey -> Value'); +hashmap.print(); + +export {}; diff --git a/ja/codes/typescript/chapter_hashing/simple_hash.ts b/ja/codes/typescript/chapter_hashing/simple_hash.ts new file mode 100644 index 000000000..07d1a876e --- /dev/null +++ b/ja/codes/typescript/chapter_hashing/simple_hash.ts @@ -0,0 +1,60 @@ +/** + * File: simple_hash.ts + * Created Time: 2023-08-06 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +/* 加算ハッシュ */ +function addHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* 乗算ハッシュ */ +function mulHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = (31 * hash + c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* XOR ハッシュ */ +function xorHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash ^= c.charCodeAt(0); + } + return hash % MODULUS; +} + +/* 回転ハッシュ */ +function rotHash(key: string): number { + let hash = 0; + const MODULUS = 1000000007; + for (const c of key) { + hash = ((hash << 4) ^ (hash >> 28) ^ c.charCodeAt(0)) % MODULUS; + } + return hash; +} + +/* Driver Code */ +const key = 'Hello アルゴリズム'; + +let hash = addHash(key); +console.log('加算ハッシュ値は ' + hash); + +hash = mulHash(key); +console.log('乗算ハッシュ値は ' + hash); + +hash = xorHash(key); +console.log('XOR ハッシュ値は ' + hash); + +hash = rotHash(key); +console.log('回転ハッシュ値は ' + hash); diff --git a/ja/codes/typescript/chapter_heap/my_heap.ts b/ja/codes/typescript/chapter_heap/my_heap.ts new file mode 100644 index 000000000..2fb6c7c49 --- /dev/null +++ b/ja/codes/typescript/chapter_heap/my_heap.ts @@ -0,0 +1,155 @@ +/** + * File: my_heap.ts + * Created Time: 2023-02-07 + * Author: Justin (xiefahit@gmail.com) + */ + +import { printHeap } from '../modules/PrintUtil'; + +/* 最大ヒープクラス */ +class MaxHeap { + private maxHeap: number[]; + /* コンストラクタ。空のヒープを作成するか、入力リストからヒープを構築する */ + constructor(nums?: number[]) { + // リスト要素をそのままヒープに追加 + this.maxHeap = nums === undefined ? [] : [...nums]; + // 葉ノード以外のすべてのノードをヒープ化 + for (let i = this.parent(this.size() - 1); i >= 0; i--) { + this.siftDown(i); + } + } + + /* 左子ノードのインデックスを取得 */ + private left(i: number): number { + return 2 * i + 1; + } + + /* 右子ノードのインデックスを取得 */ + private right(i: number): number { + return 2 * i + 2; + } + + /* 親ノードのインデックスを取得 */ + private parent(i: number): number { + return Math.floor((i - 1) / 2); // 切り捨て除算 + } + + /* 要素を交換 */ + private swap(i: number, j: number): void { + const tmp = this.maxHeap[i]; + this.maxHeap[i] = this.maxHeap[j]; + this.maxHeap[j] = tmp; + } + + /* ヒープのサイズを取得 */ + public size(): number { + return this.maxHeap.length; + } + + /* ヒープが空かどうかを判定 */ + public isEmpty(): boolean { + return this.size() === 0; + } + + /* ヒープ先頭要素にアクセス */ + public peek(): number { + return this.maxHeap[0]; + } + + /* 要素をヒープに追加 */ + public push(val: number): void { + // ノードを追加 + this.maxHeap.push(val); + // 下から上へヒープ化 + this.siftUp(this.size() - 1); + } + + /* ノード i から始めて、下から上へヒープ化 */ + private siftUp(i: number): void { + while (true) { + // ノード i の親ノードを取得 + const p = this.parent(i); + // 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 + if (p < 0 || this.maxHeap[i] <= this.maxHeap[p]) break; + // 2 つのノードを交換 + this.swap(i, p); + // ループで下から上へヒープ化 + i = p; + } + } + + /* 要素をヒープから取り出す */ + public pop(): number { + // 空判定の処理 + if (this.isEmpty()) throw new RangeError('Heap is empty.'); + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + this.swap(0, this.size() - 1); + // ノードを削除 + const val = this.maxHeap.pop(); + // 上から下へヒープ化 + this.siftDown(0); + // ヒープ先頭要素を返す + return val; + } + + /* ノード i から始めて、上から下へヒープ化 */ + private siftDown(i: number): void { + while (true) { + // ノード i, l, r のうち値が最大のノードを ma とする + const l = this.left(i), + r = this.right(i); + let ma = i; + if (l < this.size() && this.maxHeap[l] > this.maxHeap[ma]) ma = l; + if (r < this.size() && this.maxHeap[r] > this.maxHeap[ma]) ma = r; + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + if (ma === i) break; + // 2 つのノードを交換 + this.swap(i, ma); + // ループで上から下へヒープ化 + i = ma; + } + } + + /* ヒープ(二分木)を出力 */ + public print(): void { + printHeap(this.maxHeap); + } + + /* ヒープから要素を取り出す */ + public getMaxHeap(): number[] { + return this.maxHeap; + } +} + +/* Driver Code */ +if (import.meta.url.endsWith(process.argv[1])) { + /* 最大ヒープを初期化 */ + const maxHeap = new MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]); + console.log('\nリストを入力してヒープを構築した後'); + maxHeap.print(); + + /* ヒープ頂点の要素を取得 */ + let peek = maxHeap.peek(); + console.log(`\nヒープの先頭要素は ${peek}`); + + /* 要素をヒープに追加 */ + const val = 7; + maxHeap.push(val); + console.log(`\n要素 ${val} をヒープに追加した後`); + maxHeap.print(); + + /* ヒープ頂点の要素を取り出す */ + peek = maxHeap.pop(); + console.log(`\nヒープの先頭要素 ${peek} を取り出した後`); + maxHeap.print(); + + /* ヒープのサイズを取得 */ + const size = maxHeap.size(); + console.log(`\nヒープの要素数は ${size}`); + + /* ヒープが空かどうかを判定 */ + const isEmpty = maxHeap.isEmpty(); + console.log(`\nヒープが空かどうか ${isEmpty}`); +} + +export { MaxHeap }; diff --git a/ja/codes/typescript/chapter_heap/top_k.ts b/ja/codes/typescript/chapter_heap/top_k.ts new file mode 100644 index 000000000..4a0261c55 --- /dev/null +++ b/ja/codes/typescript/chapter_heap/top_k.ts @@ -0,0 +1,58 @@ +/** + * File: top_k.ts + * Created Time: 2023-08-13 + * Author: Justin (xiefahit@gmail.com) + */ + +import { MaxHeap } from './my_heap'; + +/* 要素をヒープに追加 */ +function pushMinHeap(maxHeap: MaxHeap, val: number): void { + // 要素を反転する + maxHeap.push(-val); +} + +/* 要素をヒープから取り出す */ +function popMinHeap(maxHeap: MaxHeap): number { + // 要素を反転する + return -maxHeap.pop(); +} + +/* ヒープ先頭要素にアクセス */ +function peekMinHeap(maxHeap: MaxHeap): number { + // 要素を反転する + return -maxHeap.peek(); +} + +/* ヒープから要素を取り出す */ +function getMinHeap(maxHeap: MaxHeap): number[] { + // 要素を反転する + return maxHeap.getMaxHeap().map((num: number) => -num); +} + +/* ヒープに基づいて配列中の最大の k 個の要素を探す */ +function topKHeap(nums: number[], k: number): number[] { + // 最小ヒープを初期化する + // 注意: ヒープ内の全要素を反転し、最大ヒープで最小ヒープをシミュレートする + const maxHeap = new MaxHeap([]); + // 配列の先頭 k 個の要素をヒープに追加 + for (let i = 0; i < k; i++) { + pushMinHeap(maxHeap, nums[i]); + } + // k+1 番目の要素から開始し、ヒープ長を k に保つ + for (let i = k; i < nums.length; i++) { + // 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する + if (nums[i] > peekMinHeap(maxHeap)) { + popMinHeap(maxHeap); + pushMinHeap(maxHeap, nums[i]); + } + } + // ヒープ内の要素を返す + return getMinHeap(maxHeap); +} + +/* Driver Code */ +const nums = [1, 7, 6, 3, 2]; +const k = 3; +const res = topKHeap(nums, k); +console.log(`最大の ${k} 個の要素は`, res); diff --git a/ja/codes/typescript/chapter_searching/binary_search.ts b/ja/codes/typescript/chapter_searching/binary_search.ts new file mode 100644 index 000000000..c4712bd79 --- /dev/null +++ b/ja/codes/typescript/chapter_searching/binary_search.ts @@ -0,0 +1,65 @@ +/** + * File: binary_search.ts + * Created Time: 2022-12-27 + * Author: Daniel (better.sunjian@gmail.com) + */ + +/* 二分探索(両閉区間) */ +function binarySearch(nums: number[], target: number): number { + // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す + let i = 0, + j = nums.length - 1; + // ループし、探索区間が空になったら終了する(i > j で空) + while (i <= j) { + // 中点インデックス m を計算 + const m = Math.floor(i + (j - i) / 2); + if (nums[m] < target) { + // この場合、target は区間 [m+1, j] にある + i = m + 1; + } else if (nums[m] > target) { + // この場合、target は区間 [i, m-1] にある + j = m - 1; + } else { + // 目標要素が見つかったらそのインデックスを返す + return m; + } + } + return -1; // 目標要素が見つからなければ -1 を返す +} + +/* 二分探索(左閉右開区間) */ +function binarySearchLCRO(nums: number[], target: number): number { + // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す + let i = 0, + j = nums.length; + // ループし、探索区間が空になったら終了する(i = j で空) + while (i < j) { + // 中点インデックス m を計算 + const m = Math.floor(i + (j - i) / 2); + if (nums[m] < target) { + // この場合、target は区間 [m+1, j) にある + i = m + 1; + } else if (nums[m] > target) { + // この場合、target は区間 [i, m) にある + j = m; + } else { + // 目標要素が見つかったらそのインデックスを返す + return m; + } + } + return -1; // 目標要素が見つからなければ -1 を返す +} + +/* Driver Code */ +const target = 6; +const nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; + +/* 二分探索(両閉区間) */ +let index = binarySearch(nums, target); +console.info('対象要素 6 のインデックス = %d', index); + +/* 二分探索(左閉右開区間) */ +index = binarySearchLCRO(nums, target); +console.info('対象要素 6 のインデックス = %d', index); + +export {}; diff --git a/ja/codes/typescript/chapter_searching/binary_search_edge.ts b/ja/codes/typescript/chapter_searching/binary_search_edge.ts new file mode 100644 index 000000000..959f80e05 --- /dev/null +++ b/ja/codes/typescript/chapter_searching/binary_search_edge.ts @@ -0,0 +1,46 @@ +/** + * File: binary_search_edge.ts + * Created Time: 2023-08-22 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ +import { binarySearchInsertion } from './binary_search_insertion'; + +/* 最も左の target を二分探索 */ +function binarySearchLeftEdge(nums: Array, target: number): number { + // target の挿入位置を探すのと等価 + const i = binarySearchInsertion(nums, target); + // target が見つからなければ、-1 を返す + if (i === nums.length || nums[i] !== target) { + return -1; + } + // target が見つかったら、インデックス i を返す + return i; +} + +/* 最も右の target を二分探索 */ +function binarySearchRightEdge(nums: Array, target: number): number { + // 最左の target + 1 を探す問題に変換する + const i = binarySearchInsertion(nums, target + 1); + // j は最も右の target を指し、i は target より大きい最初の要素を指す + const j = i - 1; + // target が見つからなければ、-1 を返す + if (j === -1 || nums[j] !== target) { + return -1; + } + // target が見つかったら、インデックス j を返す + return j; +} + +/* Driver Code */ +// 重複要素を含む配列 +let nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; +console.log('\n配列 nums = ' + nums); +// 二分探索で左端と右端を探す +for (const target of [6, 7]) { + let index = binarySearchLeftEdge(nums, target); + console.log('一番左の要素 ' + target + ' のインデックスは ' + index); + index = binarySearchRightEdge(nums, target); + console.log('一番右の要素 ' + target + ' のインデックスは ' + index); +} + +export {}; diff --git a/ja/codes/typescript/chapter_searching/binary_search_insertion.ts b/ja/codes/typescript/chapter_searching/binary_search_insertion.ts new file mode 100644 index 000000000..7d99d5110 --- /dev/null +++ b/ja/codes/typescript/chapter_searching/binary_search_insertion.ts @@ -0,0 +1,65 @@ +/** + * File: binary_search_insertion.ts + * Created Time: 2023-08-22 + * Author: Gaofer Chou (gaofer-chou@qq.com) + */ + +/* 二分探索で挿入位置を探す(重複要素なし) */ +function binarySearchInsertionSimple( + nums: Array, + target: number +): number { + let i = 0, + j = nums.length - 1; // 両閉区間 [0, n-1] を初期化 + while (i <= j) { + const m = Math.floor(i + (j - i) / 2); // 中点インデックス m を計算し、Math.floor() で切り捨てる + if (nums[m] < target) { + i = m + 1; // target は区間 [m+1, j] にある + } else if (nums[m] > target) { + j = m - 1; // target は区間 [i, m-1] にある + } else { + return m; // target が見つかったら、挿入位置 m を返す + } + } + // target が見つからなければ、挿入位置 i を返す + return i; +} + +/* 二分探索で挿入位置を探す(重複要素あり) */ +function binarySearchInsertion(nums: Array, target: number): number { + let i = 0, + j = nums.length - 1; // 両閉区間 [0, n-1] を初期化 + while (i <= j) { + const m = Math.floor(i + (j - i) / 2); // 中点インデックス m を計算し、Math.floor() で切り捨てる + if (nums[m] < target) { + i = m + 1; // target は区間 [m+1, j] にある + } else if (nums[m] > target) { + j = m - 1; // target は区間 [i, m-1] にある + } else { + j = m - 1; // target より小さい最初の要素は区間 [i, m-1] にある + } + } + // 挿入位置 i を返す + return i; +} + +/* Driver Code */ +// 重複要素のない配列 +let nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35]; +console.log('\n配列 nums = ' + nums); +// 二分探索で挿入位置を探す +for (const target of [6, 9]) { + const index = binarySearchInsertionSimple(nums, target); + console.log('要素 ' + target + ' の挿入位置のインデックスは ' + index); +} + +// 重複要素を含む配列 +nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15]; +console.log('\n配列 nums = ' + nums); +// 二分探索で挿入位置を探す +for (const target of [2, 6, 20]) { + const index = binarySearchInsertion(nums, target); + console.log('要素 ' + target + ' の挿入位置のインデックスは ' + index); +} + +export { binarySearchInsertion }; diff --git a/ja/codes/typescript/chapter_searching/hashing_search.ts b/ja/codes/typescript/chapter_searching/hashing_search.ts new file mode 100644 index 000000000..82705ac54 --- /dev/null +++ b/ja/codes/typescript/chapter_searching/hashing_search.ts @@ -0,0 +1,50 @@ +/** + * File: hashing_search.ts + * Created Time: 2022-12-29 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +import { ListNode, arrToLinkedList } from '../modules/ListNode'; + +/* ハッシュ探索(配列) */ +function hashingSearchArray(map: Map, target: number): number { + // ハッシュテーブルの key: 目標要素、value: インデックス + // ハッシュテーブルにこの key がなければ -1 を返す + return map.has(target) ? (map.get(target) as number) : -1; +} + +/* ハッシュ探索(連結リスト) */ +function hashingSearchLinkedList( + map: Map, + target: number +): ListNode | null { + // ハッシュテーブルの key: 目標ノード値、value: ノードオブジェクト + // ハッシュテーブルにこの key がなければ null を返す + return map.has(target) ? (map.get(target) as ListNode) : null; +} + +/* Driver Code */ +const target = 3; + +/* ハッシュ探索(配列) */ +const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; +// ハッシュテーブルを初期化 +const map = new Map(); +for (let i = 0; i < nums.length; i++) { + map.set(nums[i], i); // key: 要素、value: インデックス +} +const index = hashingSearchArray(map, target); +console.log('対象要素 3 のインデックス = ' + index); + +/* ハッシュ探索(連結リスト) */ +let head = arrToLinkedList(nums); +// ハッシュテーブルを初期化 +const map1 = new Map(); +while (head != null) { + map1.set(head.val, head); // key: ノード値、value: ノード + head = head.next; +} +const node = hashingSearchLinkedList(map1, target); +console.log('対象ノード値 3 に対応するノードオブジェクトは', node); + +export {}; diff --git a/ja/codes/typescript/chapter_searching/linear_search.ts b/ja/codes/typescript/chapter_searching/linear_search.ts new file mode 100644 index 000000000..a173f0d0f --- /dev/null +++ b/ja/codes/typescript/chapter_searching/linear_search.ts @@ -0,0 +1,52 @@ +/** + * File: linear_search.ts + * Created Time: 2023-01-07 + * Author: Daniel (better.sunjian@gmail.com) + */ + +import { ListNode, arrToLinkedList } from '../modules/ListNode'; + +/* 線形探索(配列) */ +function linearSearchArray(nums: number[], target: number): number { + // 配列を走査 + for (let i = 0; i < nums.length; i++) { + // 目標要素が見つかったらそのインデックスを返す + if (nums[i] === target) { + return i; + } + } + // 目標要素が見つからなければ -1 を返す + return -1; +} + +/* 線形探索(連結リスト) */ +function linearSearchLinkedList( + head: ListNode | null, + target: number +): ListNode | null { + // 連結リストを走査 + while (head) { + // 対象ノードが見つかったら、それを返す + if (head.val === target) { + return head; + } + head = head.next; + } + // 対象ノードが見つからない場合は null を返す + return null; +} + +/* Driver Code */ +const target = 3; + +/* 配列で線形探索を行う */ +const nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8]; +const index = linearSearchArray(nums, target); +console.log('対象要素 3 のインデックス =', index); + +/* 連結リストで線形探索を行う */ +const head = arrToLinkedList(nums); +const node = linearSearchLinkedList(head, target); +console.log('対象ノード値 3 に対応するノードオブジェクトは', node); + +export {}; diff --git a/ja/codes/typescript/chapter_searching/two_sum.ts b/ja/codes/typescript/chapter_searching/two_sum.ts new file mode 100644 index 000000000..836376da8 --- /dev/null +++ b/ja/codes/typescript/chapter_searching/two_sum.ts @@ -0,0 +1,49 @@ +/** + * File: two_sum.ts + * Created Time: 2022-12-15 + * Author: gyt95 (gytkwan@gmail.com) + */ + +/* 方法 1:総当たり列挙 */ +function twoSumBruteForce(nums: number[], target: number): number[] { + const n = nums.length; + // 2重ループのため、時間計算量は O(n^2) + for (let i = 0; i < n; i++) { + for (let j = i + 1; j < n; j++) { + if (nums[i] + nums[j] === target) { + return [i, j]; + } + } + } + return []; +} + +/* 方法 2:補助ハッシュテーブル */ +function twoSumHashTable(nums: number[], target: number): number[] { + // 補助ハッシュテーブルを使用し、空間計算量は O(n) + let m: Map = new Map(); + // 単一ループで、時間計算量は O(n) + for (let i = 0; i < nums.length; i++) { + let index = m.get(target - nums[i]); + if (index !== undefined) { + return [index, i]; + } else { + m.set(nums[i], i); + } + } + return []; +} + +/* Driver Code */ +// 方法 1 +const nums = [2, 7, 11, 15], + target = 13; + +let res = twoSumBruteForce(nums, target); +console.log('方法1 res = ', res); + +// 方法 2 +res = twoSumHashTable(nums, target); +console.log('方法2 res = ', res); + +export {}; diff --git a/ja/codes/typescript/chapter_sorting/bubble_sort.ts b/ja/codes/typescript/chapter_sorting/bubble_sort.ts new file mode 100644 index 000000000..f8cbccdb9 --- /dev/null +++ b/ja/codes/typescript/chapter_sorting/bubble_sort.ts @@ -0,0 +1,51 @@ +/** + * File: bubble_sort.ts + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* バブルソート */ +function bubbleSort(nums: number[]): void { + // 外側のループ:未ソート区間は [0, i] + for (let i = nums.length - 1; i > 0; i--) { + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // nums[j] と nums[j + 1] を交換 + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } +} + +/* バブルソート(フラグ最適化) */ +function bubbleSortWithFlag(nums: number[]): void { + // 外側のループ:未ソート区間は [0, i] + for (let i = nums.length - 1; i > 0; i--) { + let flag = false; // フラグを初期化する + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + for (let j = 0; j < i; j++) { + if (nums[j] > nums[j + 1]) { + // nums[j] と nums[j + 1] を交換 + let tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; // 交換する要素を記録 + } + } + if (!flag) break; // このバブル処理で要素交換が一度もなければそのまま終了 + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +bubbleSort(nums); +console.log('バブルソート完了後 nums =', nums); + +const nums1 = [4, 1, 3, 1, 5, 2]; +bubbleSortWithFlag(nums1); +console.log('バブルソート完了後 nums =', nums1); + +export {}; diff --git a/ja/codes/typescript/chapter_sorting/bucket_sort.ts b/ja/codes/typescript/chapter_sorting/bucket_sort.ts new file mode 100644 index 000000000..d3f0a1589 --- /dev/null +++ b/ja/codes/typescript/chapter_sorting/bucket_sort.ts @@ -0,0 +1,41 @@ +/** + * File: bucket_sort.ts + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* バケットソート */ +function bucketSort(nums: number[]): void { + // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする + const k = nums.length / 2; + const buckets: number[][] = []; + for (let i = 0; i < k; i++) { + buckets.push([]); + } + // 1. 配列要素を各バケットに振り分ける + for (const num of nums) { + // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する + const i = Math.floor(num * k); + // num をバケット i に追加 + buckets[i].push(num); + } + // 2. 各バケットをソートする + for (const bucket of buckets) { + // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい + bucket.sort((a, b) => a - b); + } + // 3. バケットを走査して結果を結合 + let i = 0; + for (const bucket of buckets) { + for (const num of bucket) { + nums[i++] = num; + } + } +} + +/* Driver Code */ +const nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37]; +bucketSort(nums); +console.log('バケットソート完了後 nums =', nums); + +export {}; diff --git a/ja/codes/typescript/chapter_sorting/counting_sort.ts b/ja/codes/typescript/chapter_sorting/counting_sort.ts new file mode 100644 index 000000000..dac859804 --- /dev/null +++ b/ja/codes/typescript/chapter_sorting/counting_sort.ts @@ -0,0 +1,67 @@ +/** + * File: counting_sort.ts + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 計数ソート */ +// 簡易実装のため、オブジェクトのソートには使えない +function countingSortNaive(nums: number[]): void { + // 1. 配列の最大要素 m を求める + let m: number = Math.max(...nums); + // 2. 各数値の出現回数を数える + // counter[num] は num の出現回数を表す + const counter: number[] = new Array(m + 1).fill(0); + for (const num of nums) { + counter[num]++; + } + // 3. counter を走査し、各要素を元の配列 nums に書き戻す + let i = 0; + for (let num = 0; num < m + 1; num++) { + for (let j = 0; j < counter[num]; j++, i++) { + nums[i] = num; + } + } +} + +/* 計数ソート */ +// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである +function countingSort(nums: number[]): void { + // 1. 配列の最大要素 m を求める + let m: number = Math.max(...nums); + // 2. 各数値の出現回数を数える + // counter[num] は num の出現回数を表す + const counter: number[] = new Array(m + 1).fill(0); + for (const num of nums) { + counter[num]++; + } + // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する + // つまり counter[num]-1 は、num が res に最後に現れるインデックス + for (let i = 0; i < m; i++) { + counter[i + 1] += counter[i]; + } + // 4. nums を逆順に走査し、各要素を結果配列 res に格納する + // 結果を記録するための配列 res を初期化 + const n = nums.length; + const res: number[] = new Array(n); + for (let i = n - 1; i >= 0; i--) { + const num = nums[i]; + res[counter[num] - 1] = num; // num を対応するインデックスに配置 + counter[num]--; // 累積和を 1 減らして、次に num を配置するインデックスを得る + } + // 結果配列 res で元の配列 nums を上書きする + for (let i = 0; i < n; i++) { + nums[i] = res[i]; + } +} + +/* Driver Code */ +const nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; +countingSortNaive(nums); +console.log('カウントソート(オブジェクトはソート不可)完了後 nums =', nums); + +const nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4]; +countingSort(nums1); +console.log('計数ソート完了後 nums1 =', nums1); + +export {}; diff --git a/ja/codes/typescript/chapter_sorting/heap_sort.ts b/ja/codes/typescript/chapter_sorting/heap_sort.ts new file mode 100644 index 000000000..14de1309d --- /dev/null +++ b/ja/codes/typescript/chapter_sorting/heap_sort.ts @@ -0,0 +1,51 @@ +/** + * File: heap_sort.ts + * Created Time: 2023-06-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* ヒープの長さは n。ノード i から下方向にヒープ化 */ +function siftDown(nums: number[], n: number, i: number): void { + while (true) { + // ノード i, l, r のうち値が最大のノードを ma とする + let l = 2 * i + 1; + let r = 2 * i + 2; + let ma = i; + if (l < n && nums[l] > nums[ma]) { + ma = l; + } + if (r < n && nums[r] > nums[ma]) { + ma = r; + } + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + if (ma === i) { + break; + } + // 2 つのノードを交換 + [nums[i], nums[ma]] = [nums[ma], nums[i]]; + // ループで上から下へヒープ化 + i = ma; + } +} + +/* ヒープソート */ +function heapSort(nums: number[]): void { + // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する + for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) { + siftDown(nums, nums.length, i); + } + // ヒープから最大要素を取り出し、n-1 回繰り返す + for (let i = nums.length - 1; i > 0; i--) { + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + [nums[0], nums[i]] = [nums[i], nums[0]]; + // 根ノードを起点に、上から下へヒープ化 + siftDown(nums, i, 0); + } +} + +/* Driver Code */ +const nums: number[] = [4, 1, 3, 1, 5, 2]; +heapSort(nums); +console.log('ヒープソート完了後 nums =', nums); + +export {}; diff --git a/ja/codes/typescript/chapter_sorting/insertion_sort.ts b/ja/codes/typescript/chapter_sorting/insertion_sort.ts new file mode 100644 index 000000000..000647cfc --- /dev/null +++ b/ja/codes/typescript/chapter_sorting/insertion_sort.ts @@ -0,0 +1,27 @@ +/** + * File: insertion_sort.ts + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 挿入ソート */ +function insertionSort(nums: number[]): void { + // 外側ループ:整列済み区間は [0, i-1] + for (let i = 1; i < nums.length; i++) { + const base = nums[i]; + let j = i - 1; + // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する + while (j >= 0 && nums[j] > base) { + nums[j + 1] = nums[j]; // nums[j] を 1 つ右へ移動する + j--; + } + nums[j + 1] = base; // base を正しい位置に配置する + } +} + +/* Driver Code */ +const nums = [4, 1, 3, 1, 5, 2]; +insertionSort(nums); +console.log('挿入ソート完了後 nums =', nums); + +export {}; diff --git a/ja/codes/typescript/chapter_sorting/merge_sort.ts b/ja/codes/typescript/chapter_sorting/merge_sort.ts new file mode 100644 index 000000000..e10648920 --- /dev/null +++ b/ja/codes/typescript/chapter_sorting/merge_sort.ts @@ -0,0 +1,54 @@ +/** + * File: merge_sort.ts + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 左部分配列と右部分配列をマージ */ +function merge(nums: number[], left: number, mid: number, right: number): void { + // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right] + // マージ結果を格納する一時配列 tmp を作成 + const tmp = new Array(right - left + 1); + // 左右の部分配列の開始インデックスを初期化する + let i = left, + j = mid + 1, + k = 0; + // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする + while (i <= mid && j <= right) { + if (nums[i] <= nums[j]) { + tmp[k++] = nums[i++]; + } else { + tmp[k++] = nums[j++]; + } + } + // 左右の部分配列の残り要素を一時配列にコピーする + while (i <= mid) { + tmp[k++] = nums[i++]; + } + while (j <= right) { + tmp[k++] = nums[j++]; + } + // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする + for (k = 0; k < tmp.length; k++) { + nums[left + k] = tmp[k]; + } +} + +/* マージソート */ +function mergeSort(nums: number[], left: number, right: number): void { + // 終了条件 + if (left >= right) return; // 部分配列の長さが 1 になったら再帰を終了 + // 分割フェーズ + let mid = Math.floor(left + (right - left) / 2); // 中点を計算 + mergeSort(nums, left, mid); // 左部分配列を再帰処理 + mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理 + // マージフェーズ + merge(nums, left, mid, right); +} + +/* Driver Code */ +const nums = [7, 3, 2, 6, 0, 1, 5, 4]; +mergeSort(nums, 0, nums.length - 1); +console.log('マージソート完了後 nums =', nums); + +export {}; diff --git a/ja/codes/typescript/chapter_sorting/quick_sort.ts b/ja/codes/typescript/chapter_sorting/quick_sort.ts new file mode 100644 index 000000000..ad53c9bab --- /dev/null +++ b/ja/codes/typescript/chapter_sorting/quick_sort.ts @@ -0,0 +1,180 @@ +/** + * File: quick_sort.ts + * Created Time: 2022-12-12 + * Author: Justin (xiefahit@gmail.com) + */ + +/* クイックソートクラス */ +class QuickSort { + /* 要素の交換 */ + swap(nums: number[], i: number, j: number): void { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 番兵分割 */ + partition(nums: number[], left: number, right: number): number { + // nums[left] を基準値とする + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j -= 1; // 右から左へ基準値未満の最初の要素を探す + } + while (i < j && nums[i] <= nums[left]) { + i += 1; // 左から右へ基準値より大きい最初の要素を探す + } + // 要素の交換 + this.swap(nums, i, j); // この 2 つの要素を交換 + } + this.swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する + return i; // 基準値のインデックスを返す + } + + /* クイックソート */ + quickSort(nums: number[], left: number, right: number): void { + // 部分配列の長さが 1 なら再帰を終了する + if (left >= right) { + return; + } + // 番兵分割 + const pivot = this.partition(nums, left, right); + // 左右の部分配列を再帰処理 + this.quickSort(nums, left, pivot - 1); + this.quickSort(nums, pivot + 1, right); + } +} + +/* クイックソートクラス(中央値ピボット最適化) */ +class QuickSortMedian { + /* 要素の交換 */ + swap(nums: number[], i: number, j: number): void { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 3つの候補要素の中央値を選ぶ */ + medianThree( + nums: number[], + left: number, + mid: number, + right: number + ): number { + let l = nums[left], + m = nums[mid], + r = nums[right]; + // m は l と r の間 + if ((l <= m && m <= r) || (r <= m && m <= l)) return mid; + // l は m と r の間 + if ((m <= l && l <= r) || (r <= l && l <= m)) return left; + return right; + } + + /* 番兵による分割処理(3 点中央値) */ + partition(nums: number[], left: number, right: number): number { + // 3つの候補要素の中央値を選ぶ + let med = this.medianThree( + nums, + left, + Math.floor((left + right) / 2), + right + ); + // 中央値を配列の最左端に交換する + this.swap(nums, left, med); + // nums[left] を基準値とする + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j--; // 右から左へ基準値未満の最初の要素を探す + } + while (i < j && nums[i] <= nums[left]) { + i++; // 左から右へ基準値より大きい最初の要素を探す + } + this.swap(nums, i, j); // この 2 つの要素を交換 + } + this.swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する + return i; // 基準値のインデックスを返す + } + + /* クイックソート */ + quickSort(nums: number[], left: number, right: number): void { + // 部分配列の長さが 1 なら再帰を終了する + if (left >= right) { + return; + } + // 番兵分割 + const pivot = this.partition(nums, left, right); + // 左右の部分配列を再帰処理 + this.quickSort(nums, left, pivot - 1); + this.quickSort(nums, pivot + 1, right); + } +} + +/* クイックソートクラス(再帰深度最適化) */ +class QuickSortTailCall { + /* 要素の交換 */ + swap(nums: number[], i: number, j: number): void { + let tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 番兵分割 */ + partition(nums: number[], left: number, right: number): number { + // nums[left] を基準値とする + let i = left, + j = right; + while (i < j) { + while (i < j && nums[j] >= nums[left]) { + j--; // 右から左へ基準値未満の最初の要素を探す + } + while (i < j && nums[i] <= nums[left]) { + i++; // 左から右へ基準値より大きい最初の要素を探す + } + this.swap(nums, i, j); // この 2 つの要素を交換 + } + this.swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する + return i; // 基準値のインデックスを返す + } + + /* クイックソート(再帰深度最適化) */ + quickSort(nums: number[], left: number, right: number): void { + // 部分配列の長さが 1 なら終了 + while (left < right) { + // 番兵による分割処理 + let pivot = this.partition(nums, left, right); + // 2 つの部分配列のうち短いほうにクイックソートを適用する + if (pivot - left < right - pivot) { + this.quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソート + left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right] + } else { + this.quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソート + right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1] + } + } + } +} + +/* Driver Code */ +/* クイックソート */ +const nums = [2, 4, 1, 0, 3, 5]; +const quickSort = new QuickSort(); +quickSort.quickSort(nums, 0, nums.length - 1); +console.log('クイックソート完了後 nums =', nums); + +/* クイックソート(中央値の基準値で最適化) */ +const nums1 = [2, 4, 1, 0, 3, 5]; +const quickSortMedian = new QuickSortMedian(); +quickSortMedian.quickSort(nums1, 0, nums1.length - 1); +console.log('クイックソート(中央値ピボット最適化)完了後 nums =', nums1); + +/* クイックソート(再帰深度最適化) */ +const nums2 = [2, 4, 1, 0, 3, 5]; +const quickSortTailCall = new QuickSortTailCall(); +quickSortTailCall.quickSort(nums2, 0, nums2.length - 1); +console.log('クイックソート(再帰深度最適化)完了後 nums =', nums2); + +export {}; diff --git a/ja/codes/typescript/chapter_sorting/radix_sort.ts b/ja/codes/typescript/chapter_sorting/radix_sort.ts new file mode 100644 index 000000000..62922e674 --- /dev/null +++ b/ja/codes/typescript/chapter_sorting/radix_sort.ts @@ -0,0 +1,63 @@ +/** + * File: radix_sort.ts + * Created Time: 2023-04-08 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */ +function digit(num: number, exp: number): number { + // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す + return Math.floor(num / exp) % 10; +} + +/* 計数ソート(nums の k 桁目でソート) */ +function countingSortDigit(nums: number[], exp: number): void { + // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 + const counter = new Array(10).fill(0); + const n = nums.length; + // 0~9 の各数字の出現回数を集計する + for (let i = 0; i < n; i++) { + const d = digit(nums[i], exp); // nums[i] の第 k 位を取得し、d とする + counter[d]++; // 数字 d の出現回数を数える + } + // 累積和を求め、「出現回数」を「配列インデックス」に変換する + for (let i = 1; i < 10; i++) { + counter[i] += counter[i - 1]; + } + // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する + const res = new Array(n).fill(0); + for (let i = n - 1; i >= 0; i--) { + const d = digit(nums[i], exp); + const j = counter[d] - 1; // d の配列内インデックス j を取得する + res[j] = nums[i]; // 現在の要素をインデックス j に格納する + counter[d]--; // d の個数を 1 減らす + } + // 結果で元の配列 nums を上書きする + for (let i = 0; i < n; i++) { + nums[i] = res[i]; + } +} + +/* 基数ソート */ +function radixSort(nums: number[]): void { + // 最大桁数の判定用に配列の最大要素を取得 + let m: number = Math.max(... nums); + // 下位桁から上位桁の順に走査する + for (let exp = 1; exp <= m; exp *= 10) { + // 配列要素の k 桁目に対して計数ソートを行う + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // つまり exp = 10^(k-1) + countingSortDigit(nums, exp); + } +} + +/* Driver Code */ +const nums = [ + 10546151, 35663510, 42865989, 34862445, 81883077, 88906420, 72429244, + 30524779, 82060337, 63832996, +]; +radixSort(nums); +console.log('基数ソート完了後 nums =', nums); + +export {}; diff --git a/ja/codes/typescript/chapter_sorting/selection_sort.ts b/ja/codes/typescript/chapter_sorting/selection_sort.ts new file mode 100644 index 000000000..1979ea1a3 --- /dev/null +++ b/ja/codes/typescript/chapter_sorting/selection_sort.ts @@ -0,0 +1,29 @@ +/** + * File: selection_sort.ts + * Created Time: 2023-06-04 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 選択ソート */ +function selectionSort(nums: number[]): void { + let n = nums.length; + // 外側ループ:未整列区間は [i, n-1] + for (let i = 0; i < n - 1; i++) { + // 内側のループ:未ソート区間の最小要素を見つける + let k = i; + for (let j = i + 1; j < n; j++) { + if (nums[j] < nums[k]) { + k = j; // 最小要素のインデックスを記録 + } + } + // その最小要素を未整列区間の先頭要素と交換する + [nums[i], nums[k]] = [nums[k], nums[i]]; + } +} + +/* Driver Code */ +const nums: number[] = [4, 1, 3, 1, 5, 2]; +selectionSort(nums); +console.log('選択ソート完了後 nums =', nums); + +export {}; diff --git a/ja/codes/typescript/chapter_stack_and_queue/array_deque.ts b/ja/codes/typescript/chapter_stack_and_queue/array_deque.ts new file mode 100644 index 000000000..bb8048f19 --- /dev/null +++ b/ja/codes/typescript/chapter_stack_and_queue/array_deque.ts @@ -0,0 +1,158 @@ +/** + * File: array_deque.ts + * Created Time: 2023-02-28 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 循環配列ベースの両端キュー */ +class ArrayDeque { + private nums: number[]; // 両端キューの要素を格納する配列 + private front: number; // 先頭ポインタ。先頭要素を指す + private queSize: number; // 両端キューの長さ + + /* コンストラクタ */ + constructor(capacity: number) { + this.nums = new Array(capacity); + this.front = 0; + this.queSize = 0; + } + + /* 両端キューの容量を取得 */ + capacity(): number { + return this.nums.length; + } + + /* 両端キューの長さを取得 */ + size(): number { + return this.queSize; + } + + /* 両端キューが空かどうかを判定 */ + isEmpty(): boolean { + return this.queSize === 0; + } + + /* 循環配列のインデックスを計算 */ + index(i: number): number { + // 剰余演算により配列の先頭と末尾をつなげる + // i が配列の末尾を越えたら先頭に戻る + // i が配列の先頭を越えて前に出たら末尾に戻る + return (i + this.capacity()) % this.capacity(); + } + + /* キュー先頭にエンキュー */ + pushFirst(num: number): void { + if (this.queSize === this.capacity()) { + console.log('両端キューは満杯です'); + return; + } + // 先頭ポインタを左に 1 つ移動する + // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする + this.front = this.index(this.front - 1); + // num をキュー先頭に追加 + this.nums[this.front] = num; + this.queSize++; + } + + /* キュー末尾にエンキュー */ + pushLast(num: number): void { + if (this.queSize === this.capacity()) { + console.log('両端キューは満杯です'); + return; + } + // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す + const rear: number = this.index(this.front + this.queSize); + // num をキュー末尾に追加 + this.nums[rear] = num; + this.queSize++; + } + + /* キュー先頭からデキュー */ + popFirst(): number { + const num: number = this.peekFirst(); + // 先頭ポインタを 1 つ後ろへ進める + this.front = this.index(this.front + 1); + this.queSize--; + return num; + } + + /* キュー末尾からデキュー */ + popLast(): number { + const num: number = this.peekLast(); + this.queSize--; + return num; + } + + /* キュー先頭の要素にアクセス */ + peekFirst(): number { + if (this.isEmpty()) throw new Error('The Deque Is Empty.'); + return this.nums[this.front]; + } + + /* キュー末尾の要素にアクセス */ + peekLast(): number { + if (this.isEmpty()) throw new Error('The Deque Is Empty.'); + // 末尾要素のインデックスを計算 + const last = this.index(this.front + this.queSize - 1); + return this.nums[last]; + } + + /* 出力用の配列を返す */ + toArray(): number[] { + // 有効長の範囲内のリスト要素のみを変換 + const res: number[] = []; + for (let i = 0, j = this.front; i < this.queSize; i++, j++) { + res[i] = this.nums[this.index(j)]; + } + return res; + } +} + +/* Driver Code */ +/* 両端キューを初期化 */ +const capacity = 5; +const deque: ArrayDeque = new ArrayDeque(capacity); +deque.pushLast(3); +deque.pushLast(2); +deque.pushLast(5); +console.log('両端キュー deque = [' + deque.toArray() + ']'); + +/* 要素にアクセス */ +const peekFirst = deque.peekFirst(); +console.log('先頭要素 peekFirst = ' + peekFirst); +const peekLast = deque.peekLast(); +console.log('末尾要素 peekLast = ' + peekLast); + +/* 要素をエンキュー */ +deque.pushLast(4); +console.log('要素 4 を末尾に追加後 deque = [' + deque.toArray() + ']'); +deque.pushFirst(1); +console.log('要素 1 を先頭に追加後 deque = [' + deque.toArray() + ']'); + +/* 要素をデキュー */ +const popLast = deque.popLast(); +console.log( + '末尾から取り出した要素 = ' + + popLast + + '、末尾から取り出し後 deque = [' + + deque.toArray() + + ']' +); +const popFirst = deque.popFirst(); +console.log( + '先頭から取り出した要素 = ' + + popFirst + + '、先頭から取り出し後 deque = [' + + deque.toArray() + + ']' +); + +/* 両端キューの長さを取得 */ +const size = deque.size(); +console.log('両端キューの長さ size = ' + size); + +/* 両端キューが空かどうかを判定 */ +const isEmpty = deque.isEmpty(); +console.log('両端キューが空かどうか = ' + isEmpty); + +export {}; diff --git a/ja/codes/typescript/chapter_stack_and_queue/array_queue.ts b/ja/codes/typescript/chapter_stack_and_queue/array_queue.ts new file mode 100644 index 000000000..84cb2f1c6 --- /dev/null +++ b/ja/codes/typescript/chapter_stack_and_queue/array_queue.ts @@ -0,0 +1,109 @@ +/** + * File: array_queue.ts + * Created Time: 2022-12-11 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* 循環配列ベースのキュー */ +class ArrayQueue { + private nums: number[]; // キュー要素を格納する配列 + private front: number; // 先頭ポインタ。先頭要素を指す + private queSize: number; // キューの長さ + + constructor(capacity: number) { + this.nums = new Array(capacity); + this.front = this.queSize = 0; + } + + /* キューの容量を取得 */ + get capacity(): number { + return this.nums.length; + } + + /* キューの長さを取得 */ + get size(): number { + return this.queSize; + } + + /* キューが空かどうかを判定 */ + isEmpty(): boolean { + return this.queSize === 0; + } + + /* エンキュー */ + push(num: number): void { + if (this.size === this.capacity) { + console.log('キューは満杯です'); + return; + } + // 末尾ポインタを計算し、末尾インデックス + 1 を指す + // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする + const rear = (this.front + this.queSize) % this.capacity; + // num をキュー末尾に追加 + this.nums[rear] = num; + this.queSize++; + } + + /* デキュー */ + pop(): number { + const num = this.peek(); + // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す + this.front = (this.front + 1) % this.capacity; + this.queSize--; + return num; + } + + /* キュー先頭の要素にアクセス */ + peek(): number { + if (this.isEmpty()) throw new Error('キューが空です'); + return this.nums[this.front]; + } + + /* Array を返す */ + toArray(): number[] { + // 有効長の範囲内のリスト要素のみを変換 + const arr = new Array(this.size); + for (let i = 0, j = this.front; i < this.size; i++, j++) { + arr[i] = this.nums[j % this.capacity]; + } + return arr; + } +} + +/* Driver Code */ +/* キューを初期化 */ +const capacity = 10; +const queue = new ArrayQueue(capacity); + +/* 要素をエンキュー */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('キュー queue =', queue.toArray()); + +/* キュー先頭の要素にアクセス */ +const peek = queue.peek(); +console.log('先頭要素 peek = ' + peek); + +/* 要素をデキュー */ +const pop = queue.pop(); +console.log('デキューした要素 pop = ' + pop + ',デキュー後 queue =', queue.toArray()); + +/* キューの長さを取得 */ +const size = queue.size; +console.log('キューの長さ size = ' + size); + +/* キューが空かどうかを判定 */ +const isEmpty = queue.isEmpty(); +console.log('キューが空かどうか = ' + isEmpty); + +/* 循環配列をテストする */ +for (let i = 0; i < 10; i++) { + queue.push(i); + queue.pop(); + console.log('第 ' + i + ' 回目のエンキュー + デキュー後 queue =', queue.toArray()); +} + +export {}; diff --git a/ja/codes/typescript/chapter_stack_and_queue/array_stack.ts b/ja/codes/typescript/chapter_stack_and_queue/array_stack.ts new file mode 100644 index 000000000..e34d5024f --- /dev/null +++ b/ja/codes/typescript/chapter_stack_and_queue/array_stack.ts @@ -0,0 +1,77 @@ +/** + * File: array_stack.ts + * Created Time: 2022-12-08 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* 配列ベースのスタック */ +class ArrayStack { + private stack: number[]; + constructor() { + this.stack = []; + } + + /* スタックの長さを取得 */ + get size(): number { + return this.stack.length; + } + + /* スタックが空かどうかを判定 */ + isEmpty(): boolean { + return this.stack.length === 0; + } + + /* プッシュ */ + push(num: number): void { + this.stack.push(num); + } + + /* ポップ */ + pop(): number | undefined { + if (this.isEmpty()) throw new Error('スタックが空です'); + return this.stack.pop(); + } + + /* スタックトップの要素にアクセス */ + top(): number | undefined { + if (this.isEmpty()) throw new Error('スタックが空です'); + return this.stack[this.stack.length - 1]; + } + + /* Array を返す */ + toArray() { + return this.stack; + } +} + +/* Driver Code */ +/* スタックを初期化 */ +const stack = new ArrayStack(); + +/* 要素をプッシュ */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('スタック stack = '); +console.log(stack.toArray()); + +/* スタックトップの要素にアクセス */ +const top = stack.top(); +console.log('スタックトップ要素 top = ' + top); + +/* 要素をポップ */ +const pop = stack.pop(); +console.log('ポップした要素 pop = ' + pop + ',ポップ後 stack = '); +console.log(stack.toArray()); + +/* スタックの長さを取得 */ +const size = stack.size; +console.log('スタックの長さ size = ' + size); + +/* 空かどうかを判定 */ +const isEmpty = stack.isEmpty(); +console.log('スタックが空かどうか = ' + isEmpty); + +export {}; diff --git a/ja/codes/typescript/chapter_stack_and_queue/deque.ts b/ja/codes/typescript/chapter_stack_and_queue/deque.ts new file mode 100644 index 000000000..0eb5a3502 --- /dev/null +++ b/ja/codes/typescript/chapter_stack_and_queue/deque.ts @@ -0,0 +1,46 @@ +/** + * File: deque.ts + * Created Time: 2023-01-17 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* Driver Code */ +/* 両端キューを初期化 */ +// TypeScript には組み込みの両端キューがないため、Array を両端キューとして使う +const deque: number[] = []; + +/* 要素をエンキュー */ +deque.push(2); +deque.push(5); +deque.push(4); +// 注意: 配列であるため、unshift() メソッドの時間計算量は O(n) +deque.unshift(3); +deque.unshift(1); +console.log('両端キュー deque = ', deque); + +/* 要素にアクセス */ +const peekFirst: number = deque[0]; +console.log('先頭要素 peekFirst = ' + peekFirst); +const peekLast: number = deque[deque.length - 1]; +console.log('末尾要素 peekLast = ' + peekLast); + +/* 要素をデキュー */ +// 注意: 配列であるため、shift() メソッドの時間計算量は O(n) +const popFront: number = deque.shift() as number; +console.log( + '先頭からデキューした要素 popFront = ' + popFront + ',先頭からデキュー後 deque = ' + deque +); +const popBack: number = deque.pop() as number; +console.log( + '末尾からデキューした要素 popBack = ' + popBack + ',末尾からデキュー後 deque = ' + deque +); + +/* 両端キューの長さを取得 */ +const size: number = deque.length; +console.log('両端キューの長さ size = ' + size); + +/* 両端キューが空かどうかを判定 */ +const isEmpty: boolean = size === 0; +console.log('両端キューが空かどうか = ' + isEmpty); + +export {}; diff --git a/ja/codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts b/ja/codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts new file mode 100644 index 000000000..f988a4230 --- /dev/null +++ b/ja/codes/typescript/chapter_stack_and_queue/linkedlist_deque.ts @@ -0,0 +1,167 @@ +/** + * File: linkedlist_deque.ts + * Created Time: 2023-02-04 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 双方向連結リストノード */ +class ListNode { + prev: ListNode; // 前駆ノードへの参照(ポインタ) + next: ListNode; // 後継ノードへの参照(ポインタ) + val: number; // ノード値 + + constructor(val: number) { + this.val = val; + this.next = null; + this.prev = null; + } +} + +/* 双方向連結リストベースの両端キュー */ +class LinkedListDeque { + private front: ListNode; // 先頭ノード front + private rear: ListNode; // 末尾ノード rear + private queSize: number; // 両端キューの長さ + + constructor() { + this.front = null; + this.rear = null; + this.queSize = 0; + } + + /* 末尾へのエンキュー操作 */ + pushLast(val: number): void { + const node: ListNode = new ListNode(val); + // 連結リストが空なら、front と rear の両方を node に向ける + if (this.queSize === 0) { + this.front = node; + this.rear = node; + } else { + // node を連結リストの末尾に追加 + this.rear.next = node; + node.prev = this.rear; + this.rear = node; // 末尾ノードを更新する + } + this.queSize++; + } + + /* 先頭へのエンキュー操作 */ + pushFirst(val: number): void { + const node: ListNode = new ListNode(val); + // 連結リストが空なら、front と rear の両方を node に向ける + if (this.queSize === 0) { + this.front = node; + this.rear = node; + } else { + // node を連結リストの先頭に追加 + this.front.prev = node; + node.next = this.front; + this.front = node; // 先頭ノードを更新する + } + this.queSize++; + } + + /* キュー末尾からの取り出し */ + popLast(): number { + if (this.queSize === 0) { + return null; + } + const value: number = this.rear.val; // 末尾ノードの値を保存する + // 末尾ノードを削除 + let temp: ListNode = this.rear.prev; + if (temp !== null) { + temp.next = null; + this.rear.prev = null; + } + this.rear = temp; // 末尾ノードを更新する + this.queSize--; + return value; + } + + /* キュー先頭からの取り出し */ + popFirst(): number { + if (this.queSize === 0) { + return null; + } + const value: number = this.front.val; // 末尾ノードの値を保存する + // 先頭ノードを削除 + let temp: ListNode = this.front.next; + if (temp !== null) { + temp.prev = null; + this.front.next = null; + } + this.front = temp; // 先頭ノードを更新する + this.queSize--; + return value; + } + + /* キュー末尾の要素にアクセス */ + peekLast(): number { + return this.queSize === 0 ? null : this.rear.val; + } + + /* キュー先頭の要素にアクセス */ + peekFirst(): number { + return this.queSize === 0 ? null : this.front.val; + } + + /* 両端キューの長さを取得 */ + size(): number { + return this.queSize; + } + + /* 両端キューが空かどうかを判定 */ + isEmpty(): boolean { + return this.queSize === 0; + } + + /* 両端キューを出力する */ + print(): void { + const arr: number[] = []; + let temp: ListNode = this.front; + while (temp !== null) { + arr.push(temp.val); + temp = temp.next; + } + console.log('[' + arr.join(', ') + ']'); + } +} + +/* Driver Code */ +/* 両端キューを初期化 */ +const linkedListDeque: LinkedListDeque = new LinkedListDeque(); +linkedListDeque.pushLast(3); +linkedListDeque.pushLast(2); +linkedListDeque.pushLast(5); +console.log('両端キュー linkedListDeque = '); +linkedListDeque.print(); + +/* 要素にアクセス */ +const peekFirst: number = linkedListDeque.peekFirst(); +console.log('先頭要素 peekFirst = ' + peekFirst); +const peekLast: number = linkedListDeque.peekLast(); +console.log('末尾要素 peekLast = ' + peekLast); + +/* 要素をエンキュー */ +linkedListDeque.pushLast(4); +console.log('要素 4 を末尾にエンキュー後 linkedListDeque = '); +linkedListDeque.print(); +linkedListDeque.pushFirst(1); +console.log('要素 1 を先頭にエンキュー後 linkedListDeque = '); +linkedListDeque.print(); + +/* 要素をデキュー */ +const popLast: number = linkedListDeque.popLast(); +console.log('末尾からデキューした要素 = ' + popLast + ',末尾からデキュー後 linkedListDeque = '); +linkedListDeque.print(); +const popFirst: number = linkedListDeque.popFirst(); +console.log('先頭から取り出した要素 = ' + popFirst + ',先頭を取り出した後の linkedListDeque = '); +linkedListDeque.print(); + +/* 両端キューの長さを取得 */ +const size: number = linkedListDeque.size(); +console.log('両端キューの長さ size = ' + size); + +/* 両端キューが空かどうかを判定 */ +const isEmpty: boolean = linkedListDeque.isEmpty(); +console.log('両端キューが空かどうか = ' + isEmpty); diff --git a/ja/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts b/ja/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts new file mode 100644 index 000000000..c85b55ab9 --- /dev/null +++ b/ja/codes/typescript/chapter_stack_and_queue/linkedlist_queue.ts @@ -0,0 +1,102 @@ +/** + * File: linkedlist_queue.ts + * Created Time: 2022-12-19 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +import { ListNode } from '../modules/ListNode'; + +/* 連結リストベースのキュー */ +class LinkedListQueue { + private front: ListNode | null; // 先頭ノード front + private rear: ListNode | null; // 末尾ノード rear + private queSize: number = 0; + + constructor() { + this.front = null; + this.rear = null; + } + + /* キューの長さを取得 */ + get size(): number { + return this.queSize; + } + + /* キューが空かどうかを判定 */ + isEmpty(): boolean { + return this.size === 0; + } + + /* エンキュー */ + push(num: number): void { + // 末尾ノードの後ろに num を追加 + const node = new ListNode(num); + // キューが空なら、先頭・末尾ノードをともにそのノードに設定 + if (!this.front) { + this.front = node; + this.rear = node; + // キューが空でなければ、そのノードを末尾ノードの後ろに追加 + } else { + this.rear!.next = node; + this.rear = node; + } + this.queSize++; + } + + /* デキュー */ + pop(): number { + const num = this.peek(); + if (!this.front) throw new Error('キューが空です'); + // 先頭ノードを削除 + this.front = this.front.next; + this.queSize--; + return num; + } + + /* キュー先頭の要素にアクセス */ + peek(): number { + if (this.size === 0) throw new Error('キューが空です'); + return this.front!.val; + } + + /* 連結リストを Array に変換して返す */ + toArray(): number[] { + let node = this.front; + const res = new Array(this.size); + for (let i = 0; i < res.length; i++) { + res[i] = node!.val; + node = node!.next; + } + return res; + } +} + +/* Driver Code */ +/* キューを初期化 */ +const queue = new LinkedListQueue(); + +/* 要素をエンキュー */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('キュー queue = ' + queue.toArray()); + +/* キュー先頭の要素にアクセス */ +const peek = queue.peek(); +console.log('先頭要素 peek = ' + peek); + +/* 要素をデキュー */ +const pop = queue.pop(); +console.log('デキューした要素 pop = ' + pop + ',デキュー後の queue = ' + queue.toArray()); + +/* キューの長さを取得 */ +const size = queue.size; +console.log('キューの長さ size = ' + size); + +/* キューが空かどうかを判定 */ +const isEmpty = queue.isEmpty(); +console.log('キューが空かどうか = ' + isEmpty); + +export {}; diff --git a/ja/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts b/ja/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts new file mode 100644 index 000000000..e26c33a44 --- /dev/null +++ b/ja/codes/typescript/chapter_stack_and_queue/linkedlist_stack.ts @@ -0,0 +1,91 @@ +/** + * File: linkedlist_stack.ts + * Created Time: 2022-12-21 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +import { ListNode } from '../modules/ListNode'; + +/* 連結リストベースのスタック */ +class LinkedListStack { + private stackPeek: ListNode | null; // 先頭ノードをスタックトップとする + private stkSize: number = 0; // スタックの長さ + + constructor() { + this.stackPeek = null; + } + + /* スタックの長さを取得 */ + get size(): number { + return this.stkSize; + } + + /* スタックが空かどうかを判定 */ + isEmpty(): boolean { + return this.size === 0; + } + + /* プッシュ */ + push(num: number): void { + const node = new ListNode(num); + node.next = this.stackPeek; + this.stackPeek = node; + this.stkSize++; + } + + /* ポップ */ + pop(): number { + const num = this.peek(); + if (!this.stackPeek) throw new Error('スタックが空です'); + this.stackPeek = this.stackPeek.next; + this.stkSize--; + return num; + } + + /* スタックトップの要素にアクセス */ + peek(): number { + if (!this.stackPeek) throw new Error('スタックが空です'); + return this.stackPeek.val; + } + + /* 連結リストを Array に変換して返す */ + toArray(): number[] { + let node = this.stackPeek; + const res = new Array(this.size); + for (let i = res.length - 1; i >= 0; i--) { + res[i] = node!.val; + node = node!.next; + } + return res; + } +} + +/* Driver Code */ +/* スタックを初期化 */ +const stack = new LinkedListStack(); + +/* 要素をプッシュ */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('スタック stack = ' + stack.toArray()); + +/* スタックトップの要素にアクセス */ +const peek = stack.peek(); +console.log('スタックトップ要素 peek = ' + peek); + +/* 要素をポップ */ +const pop = stack.pop(); +console.log('ポップした要素 pop = ' + pop + ',ポップ後の stack = ' + stack.toArray()); + +/* スタックの長さを取得 */ +const size = stack.size; +console.log('スタックの長さ size = ' + size); + +/* 空かどうかを判定 */ +const isEmpty = stack.isEmpty(); +console.log('スタックが空かどうか = ' + isEmpty); + +export {}; diff --git a/ja/codes/typescript/chapter_stack_and_queue/queue.ts b/ja/codes/typescript/chapter_stack_and_queue/queue.ts new file mode 100644 index 000000000..9c5e9fe89 --- /dev/null +++ b/ja/codes/typescript/chapter_stack_and_queue/queue.ts @@ -0,0 +1,37 @@ +/** + * File: queue.ts + * Created Time: 2022-12-05 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* Driver Code */ +/* キューを初期化 */ +// TypeScript には組み込みのキューがないため、Array をキューとして使う +const queue: number[] = []; + +/* 要素をエンキュー */ +queue.push(1); +queue.push(3); +queue.push(2); +queue.push(5); +queue.push(4); +console.log('キュー queue =', queue); + +/* キュー先頭の要素にアクセス */ +const peek = queue[0]; +console.log('先頭要素 peek =', peek); + +/* 要素をデキュー */ +// 基盤が配列であるため、shift() メソッドの時間計算量は O(n) +const pop = queue.shift(); +console.log('デキューした要素 pop =', pop, ',デキュー後の queue = ', queue); + +/* キューの長さを取得 */ +const size = queue.length; +console.log('キューの長さ size =', size); + +/* キューが空かどうかを判定 */ +const isEmpty = queue.length === 0; +console.log('キューが空かどうか = ', isEmpty); + +export {}; diff --git a/ja/codes/typescript/chapter_stack_and_queue/stack.ts b/ja/codes/typescript/chapter_stack_and_queue/stack.ts new file mode 100644 index 000000000..3edf29d71 --- /dev/null +++ b/ja/codes/typescript/chapter_stack_and_queue/stack.ts @@ -0,0 +1,37 @@ +/** + * File: stack.ts + * Created Time: 2022-12-04 + * Author: S-N-O-R-L-A-X (snorlax.xu@outlook.com) + */ + +/* Driver Code */ +/* スタックを初期化 */ +// TypeScript には組み込みのスタッククラスがないため、Array をスタックとして使う +const stack: number[] = []; + +/* 要素をプッシュ */ +stack.push(1); +stack.push(3); +stack.push(2); +stack.push(5); +stack.push(4); +console.log('スタック stack =', stack); + +/* スタックトップの要素にアクセス */ +const peek = stack[stack.length - 1]; +console.log('スタックトップ要素 peek =', peek); + +/* 要素をポップ */ +const pop = stack.pop(); +console.log('ポップした要素 pop =', pop); +console.log('ポップ後の stack =', stack); + +/* スタックの長さを取得 */ +const size = stack.length; +console.log('スタックの長さ size =', size); + +/* 空かどうかを判定 */ +const isEmpty = stack.length === 0; +console.log('スタックが空かどうか =', isEmpty); + +export {}; diff --git a/ja/codes/typescript/chapter_tree/array_binary_tree.ts b/ja/codes/typescript/chapter_tree/array_binary_tree.ts new file mode 100644 index 000000000..b5d551927 --- /dev/null +++ b/ja/codes/typescript/chapter_tree/array_binary_tree.ts @@ -0,0 +1,151 @@ +/** + * File: array_binary_tree.js + * Created Time: 2023-08-09 + * Author: yuan0221 (yl1452491917@gmail.com) + */ + +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +type Order = 'pre' | 'in' | 'post'; + +/* 配列表現による二分木クラス */ +class ArrayBinaryTree { + #tree: (number | null)[]; + + /* コンストラクタ */ + constructor(arr: (number | null)[]) { + this.#tree = arr; + } + + /* リスト容量 */ + size(): number { + return this.#tree.length; + } + + /* インデックス i のノードの値を取得 */ + val(i: number): number | null { + // インデックスが範囲外なら、空きを表す null を返す + if (i < 0 || i >= this.size()) return null; + return this.#tree[i]; + } + + /* インデックス i のノードの左子ノードのインデックスを取得 */ + left(i: number): number { + return 2 * i + 1; + } + + /* インデックス i のノードの右子ノードのインデックスを取得 */ + right(i: number): number { + return 2 * i + 2; + } + + /* インデックス i のノードの親ノードのインデックスを取得 */ + parent(i: number): number { + return Math.floor((i - 1) / 2); // 切り捨て除算 + } + + /* レベル順走査 */ + levelOrder(): number[] { + let res = []; + // 配列を直接走査する + for (let i = 0; i < this.size(); i++) { + if (this.val(i) !== null) res.push(this.val(i)); + } + return res; + } + + /* 深さ優先探索 */ + #dfs(i: number, order: Order, res: (number | null)[]): void { + // 空きスロットなら返す + if (this.val(i) === null) return; + // 先行順走査 + if (order === 'pre') res.push(this.val(i)); + this.#dfs(this.left(i), order, res); + // 中順走査 + if (order === 'in') res.push(this.val(i)); + this.#dfs(this.right(i), order, res); + // 後順走査 + if (order === 'post') res.push(this.val(i)); + } + + /* 先行順走査 */ + preOrder(): (number | null)[] { + const res = []; + this.#dfs(0, 'pre', res); + return res; + } + + /* 中順走査 */ + inOrder(): (number | null)[] { + const res = []; + this.#dfs(0, 'in', res); + return res; + } + + /* 後順走査 */ + postOrder(): (number | null)[] { + const res = []; + this.#dfs(0, 'post', res); + return res; + } +} + +/* Driver Code */ +// 二分木を初期化 +// ここでは、配列から直接二分木を生成する関数を利用する +const arr = Array.of( + 1, + 2, + 3, + 4, + null, + 6, + 7, + 8, + 9, + null, + null, + 12, + null, + null, + 15 +); + +const root = arrToTree(arr); +console.log('\n二分木を初期化する\n'); +console.log('二分木の配列表現:'); +console.log(arr); +console.log('二分木の連結リスト表現:'); +printTree(root); + +// 配列表現による二分木クラス +const abt = new ArrayBinaryTree(arr); + +// ノードにアクセス +const i = 1; +const l = abt.left(i); +const r = abt.right(i); +const p = abt.parent(i); +console.log('\n現在のノードのインデックスは ' + i + ' 、値は ' + abt.val(i)); +console.log( + 'その左子ノードのインデックスは ' + l + ' 、値は ' + (l === null ? 'null' : abt.val(l)) +); +console.log( + 'その右子ノードのインデックスは ' + r + ' 、値は ' + (r === null ? 'null' : abt.val(r)) +); +console.log( + 'その親ノードのインデックスは ' + p + ' 、値は ' + (p === null ? 'null' : abt.val(p)) +); + +// 木を走査 +let res = abt.levelOrder(); +console.log('\nレベル順走査:' + res); +res = abt.preOrder(); +console.log('先行順走査:' + res); +res = abt.inOrder(); +console.log('中間順走査:' + res); +res = abt.postOrder(); +console.log('後行順走査:' + res); + +export {}; diff --git a/ja/codes/typescript/chapter_tree/avl_tree.ts b/ja/codes/typescript/chapter_tree/avl_tree.ts new file mode 100644 index 000000000..7e23a9340 --- /dev/null +++ b/ja/codes/typescript/chapter_tree/avl_tree.ts @@ -0,0 +1,222 @@ +/** + * File: avl_tree.ts + * Created Time: 2023-02-06 + * Author: Justin (xiefahit@gmail.com) + */ + +import { TreeNode } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* AVL 木 */ +class AVLTree { + root: TreeNode; + /* コンストラクタ */ + constructor() { + this.root = null; // 根ノード + } + + /* ノードの高さを取得 */ + height(node: TreeNode): number { + // 空ノードの高さは -1、葉ノードの高さは 0 + return node === null ? -1 : node.height; + } + + /* ノードの高さを更新する */ + private updateHeight(node: TreeNode): void { + // ノードの高さは最も高い部分木の高さ + 1 に等しい + node.height = + Math.max(this.height(node.left), this.height(node.right)) + 1; + } + + /* 平衡係数を取得 */ + balanceFactor(node: TreeNode): number { + // 空ノードの平衡係数は 0 + if (node === null) return 0; + // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ + return this.height(node.left) - this.height(node.right); + } + + /* 右回転 */ + private rightRotate(node: TreeNode): TreeNode { + const child = node.left; + const grandChild = child.right; + // child を支点として node を右回転させる + child.right = node; + node.left = grandChild; + // ノードの高さを更新する + this.updateHeight(node); + this.updateHeight(child); + // 回転後の部分木の根ノードを返す + return child; + } + + /* 左回転 */ + private leftRotate(node: TreeNode): TreeNode { + const child = node.right; + const grandChild = child.left; + // child を支点として node を左回転させる + child.left = node; + node.right = grandChild; + // ノードの高さを更新する + this.updateHeight(node); + this.updateHeight(child); + // 回転後の部分木の根ノードを返す + return child; + } + + /* 回転操作を行い、この部分木の平衡を回復する */ + private rotate(node: TreeNode): TreeNode { + // ノード node の平衡係数を取得 + const balanceFactor = this.balanceFactor(node); + // 左に偏った木 + if (balanceFactor > 1) { + if (this.balanceFactor(node.left) >= 0) { + // 右回転 + return this.rightRotate(node); + } else { + // 左回転してから右回転 + node.left = this.leftRotate(node.left); + return this.rightRotate(node); + } + } + // 右に偏った木 + if (balanceFactor < -1) { + if (this.balanceFactor(node.right) <= 0) { + // 左回転 + return this.leftRotate(node); + } else { + // 右回転してから左回転 + node.right = this.rightRotate(node.right); + return this.leftRotate(node); + } + } + // 平衡木なので回転不要、そのまま返す + return node; + } + + /* ノードを挿入 */ + insert(val: number): void { + this.root = this.insertHelper(this.root, val); + } + + /* ノードを再帰的に挿入する(補助メソッド) */ + private insertHelper(node: TreeNode, val: number): TreeNode { + if (node === null) return new TreeNode(val); + /* 1. 挿入位置を探索してノードを挿入 */ + if (val < node.val) { + node.left = this.insertHelper(node.left, val); + } else if (val > node.val) { + node.right = this.insertHelper(node.right, val); + } else { + return node; // 重複ノードは挿入せず、そのまま返す + } + this.updateHeight(node); // ノードの高さを更新する + /* 2. 回転操作を行い、部分木の平衡を回復する */ + node = this.rotate(node); + // 部分木の根ノードを返す + return node; + } + + /* ノードを削除 */ + remove(val: number): void { + this.root = this.removeHelper(this.root, val); + } + + /* ノードを再帰的に削除する(補助メソッド) */ + private removeHelper(node: TreeNode, val: number): TreeNode { + if (node === null) return null; + /* 1. ノードを探索して削除 */ + if (val < node.val) { + node.left = this.removeHelper(node.left, val); + } else if (val > node.val) { + node.right = this.removeHelper(node.right, val); + } else { + if (node.left === null || node.right === null) { + const child = node.left !== null ? node.left : node.right; + // 子ノード数 = 0 の場合、node をそのまま削除して返す + if (child === null) { + return null; + } else { + // 子ノード数 = 1 の場合、node をそのまま削除する + node = child; + } + } else { + // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える + let temp = node.right; + while (temp.left !== null) { + temp = temp.left; + } + node.right = this.removeHelper(node.right, temp.val); + node.val = temp.val; + } + } + this.updateHeight(node); // ノードの高さを更新する + /* 2. 回転操作を行い、部分木の平衡を回復する */ + node = this.rotate(node); + // 部分木の根ノードを返す + return node; + } + + /* ノードを探索 */ + search(val: number): TreeNode { + let cur = this.root; + // ループで探索し、葉ノードを越えたら抜ける + while (cur !== null) { + if (cur.val < val) { + // 目標ノードは cur の右部分木にある + cur = cur.right; + } else if (cur.val > val) { + // 目標ノードは cur の左部分木にある + cur = cur.left; + } else { + // 目標ノードが見つかったらループを抜ける + break; + } + } + // 目標ノードを返す + return cur; + } +} + +function testInsert(tree: AVLTree, val: number): void { + tree.insert(val); + console.log('\nノード ' + val + ' を挿入した後、AVL 木は'); + printTree(tree.root); +} + +function testRemove(tree: AVLTree, val: number): void { + tree.remove(val); + console.log('\nノード ' + val + ' を削除した後、AVL 木は'); + printTree(tree.root); +} + +/* Driver Code */ +/* 空の AVL 木を初期化する */ +const avlTree = new AVLTree(); +/* ノードを挿入 */ +// ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい +testInsert(avlTree, 1); +testInsert(avlTree, 2); +testInsert(avlTree, 3); +testInsert(avlTree, 4); +testInsert(avlTree, 5); +testInsert(avlTree, 8); +testInsert(avlTree, 7); +testInsert(avlTree, 9); +testInsert(avlTree, 10); +testInsert(avlTree, 6); + +/* 重複ノードを挿入する */ +testInsert(avlTree, 7); + +/* ノードを削除 */ +// ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい +testRemove(avlTree, 8); // 次数 0 のノードを削除する +testRemove(avlTree, 5); // 次数 1 のノードを削除する +testRemove(avlTree, 4); // 次数 2 のノードを削除する + +/* ノードを検索 */ +const node = avlTree.search(7); +console.log('\n見つかったノードオブジェクトは', node, '、ノードの値 = ' + node.val); + +export {}; diff --git a/ja/codes/typescript/chapter_tree/binary_search_tree.ts b/ja/codes/typescript/chapter_tree/binary_search_tree.ts new file mode 100644 index 000000000..62dda0903 --- /dev/null +++ b/ja/codes/typescript/chapter_tree/binary_search_tree.ts @@ -0,0 +1,146 @@ +/** + * File: binary_search_tree.ts + * Created Time: 2022-12-14 + * Author: Justin (xiefahit@gmail.com) + */ + +import { TreeNode } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 二分探索木 */ +class BinarySearchTree { + private root: TreeNode | null; + + /* コンストラクタ */ + constructor() { + // 空の木を初期化する + this.root = null; + } + + /* 二分木の根ノードを取得 */ + getRoot(): TreeNode | null { + return this.root; + } + + /* ノードを探索 */ + search(num: number): TreeNode | null { + let cur = this.root; + // ループで探索し、葉ノードを越えたら抜ける + while (cur !== null) { + // 目標ノードは cur の右部分木にある + if (cur.val < num) cur = cur.right; + // 目標ノードは cur の左部分木にある + else if (cur.val > num) cur = cur.left; + // 目標ノードが見つかったらループを抜ける + else break; + } + // 目標ノードを返す + return cur; + } + + /* ノードを挿入 */ + insert(num: number): void { + // 木が空なら、根ノードを初期化する + if (this.root === null) { + this.root = new TreeNode(num); + return; + } + let cur: TreeNode | null = this.root, + pre: TreeNode | null = null; + // ループで探索し、葉ノードを越えたら抜ける + while (cur !== null) { + // 重複ノードが見つかったら、直ちに返す + if (cur.val === num) return; + pre = cur; + // 挿入位置は cur の右部分木にある + if (cur.val < num) cur = cur.right; + // 挿入位置は cur の左部分木にある + else cur = cur.left; + } + // ノードを挿入 + const node = new TreeNode(num); + if (pre!.val < num) pre!.right = node; + else pre!.left = node; + } + + /* ノードを削除 */ + remove(num: number): void { + // 木が空なら、そのまま早期リターンする + if (this.root === null) return; + let cur: TreeNode | null = this.root, + pre: TreeNode | null = null; + // ループで探索し、葉ノードを越えたら抜ける + while (cur !== null) { + // 削除対象のノードが見つかったら、ループを抜ける + if (cur.val === num) break; + pre = cur; + // 削除対象ノードは cur の右部分木にある + if (cur.val < num) cur = cur.right; + // 削除対象ノードは cur の左部分木にある + else cur = cur.left; + } + // 削除対象ノードがなければそのまま返す + if (cur === null) return; + // 子ノード数 = 0 or 1 + if (cur.left === null || cur.right === null) { + // 子ノード数が 0 / 1 のとき、child = null / その子ノード + const child: TreeNode | null = + cur.left !== null ? cur.left : cur.right; + // ノード cur を削除する + if (cur !== this.root) { + if (pre!.left === cur) pre!.left = child; + else pre!.right = child; + } else { + // 削除ノードが根ノードなら、根ノードを再設定 + this.root = child; + } + } + // 子ノード数 = 2 + else { + // 中順走査における cur の次ノードを取得 + let tmp: TreeNode | null = cur.right; + while (tmp!.left !== null) { + tmp = tmp!.left; + } + // ノード tmp を再帰的に削除 + this.remove(tmp!.val); + // tmp で cur を上書きする + cur.val = tmp!.val; + } + } +} + +/* Driver Code */ +/* 二分探索木を初期化 */ +const bst = new BinarySearchTree(); +// 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる +const nums = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; +for (const num of nums) { + bst.insert(num); +} +console.log('\n初期化された二分木は\n'); +printTree(bst.getRoot()); + +/* ノードを探索 */ +const node = bst.search(7); +console.log( + '\n見つかったノードオブジェクトは ' + node + '、ノードの値 = ' + (node ? node.val : 'null') +); + +/* ノードを挿入 */ +bst.insert(16); +console.log('\nノード 16 を挿入した後、二分木は\n'); +printTree(bst.getRoot()); + +/* ノードを削除 */ +bst.remove(1); +console.log('\nノード 1 を削除した後、二分木は\n'); +printTree(bst.getRoot()); +bst.remove(2); +console.log('\nノード 2 を削除した後、二分木は\n'); +printTree(bst.getRoot()); +bst.remove(4); +console.log('\nノード 4 を削除した後、二分木は\n'); +printTree(bst.getRoot()); + +export {}; diff --git a/ja/codes/typescript/chapter_tree/binary_tree.ts b/ja/codes/typescript/chapter_tree/binary_tree.ts new file mode 100644 index 000000000..f0bc394a8 --- /dev/null +++ b/ja/codes/typescript/chapter_tree/binary_tree.ts @@ -0,0 +1,37 @@ +/** + * File: binary_tree.ts + * Created Time: 2022-12-13 + * Author: Justin (xiefahit@gmail.com) + */ + +import { TreeNode } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* 二分木を初期化 */ +// ノードを初期化 +let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); +// ノード間の参照(ポインタ)を構築する +n1.left = n2; +n1.right = n3; +n2.left = n4; +n2.right = n5; +console.log('\n二分木を初期化する\n'); +printTree(n1); + +/* ノードの挿入と削除 */ +const P = new TreeNode(0); +// n1 -> n2 の間にノード P を挿入 +n1.left = P; +P.left = n2; +console.log('\nノード P を挿入した後\n'); +printTree(n1); +// ノード P を削除 +n1.left = n2; +console.log('\nノード P を削除した後\n'); +printTree(n1); + +export {}; diff --git a/ja/codes/typescript/chapter_tree/binary_tree_bfs.ts b/ja/codes/typescript/chapter_tree/binary_tree_bfs.ts new file mode 100644 index 000000000..e0941c7f6 --- /dev/null +++ b/ja/codes/typescript/chapter_tree/binary_tree_bfs.ts @@ -0,0 +1,41 @@ +/** + * File: binary_tree_bfs.ts + * Created Time: 2022-12-14 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +/* レベル順走査 */ +function levelOrder(root: TreeNode | null): number[] { + // キューを初期化し、ルートノードを追加する + const queue = [root]; + // 走査順序を保存するためのリストを初期化する + const list: number[] = []; + while (queue.length) { + let node = queue.shift() as TreeNode; // デキュー + list.push(node.val); // ノードの値を保存する + if (node.left) { + queue.push(node.left); // 左子ノードをキューに追加 + } + if (node.right) { + queue.push(node.right); // 右子ノードをキューに追加 + } + } + return list; +} + +/* Driver Code */ +/* 二分木を初期化 */ +// ここでは、配列から直接二分木を生成する関数を利用する +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); +console.log('\n二分木を初期化する\n'); +printTree(root); + +/* レベル順走査 */ +const list = levelOrder(root); +console.log('\nレベル順走査のノード出力シーケンス = ' + list); + +export {}; diff --git a/ja/codes/typescript/chapter_tree/binary_tree_dfs.ts b/ja/codes/typescript/chapter_tree/binary_tree_dfs.ts new file mode 100644 index 000000000..475f1010c --- /dev/null +++ b/ja/codes/typescript/chapter_tree/binary_tree_dfs.ts @@ -0,0 +1,69 @@ +/** + * File: binary_tree_dfs.ts + * Created Time: 2022-12-14 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../modules/TreeNode'; +import { arrToTree } from '../modules/TreeNode'; +import { printTree } from '../modules/PrintUtil'; + +// 走査順序を格納するリストを初期化 +const list: number[] = []; + +/* 先行順走査 */ +function preOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // 訪問順序:根ノード -> 左部分木 -> 右部分木 + list.push(root.val); + preOrder(root.left); + preOrder(root.right); +} + +/* 中順走査 */ +function inOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 + inOrder(root.left); + list.push(root.val); + inOrder(root.right); +} + +/* 後順走査 */ +function postOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード + postOrder(root.left); + postOrder(root.right); + list.push(root.val); +} + +/* Driver Code */ +/* 二分木を初期化 */ +// ここでは、配列から直接二分木を生成する関数を利用する +const root = arrToTree([1, 2, 3, 4, 5, 6, 7]); +console.log('\n二分木を初期化する\n'); +printTree(root); + +/* 先行順走査 */ +list.length = 0; +preOrder(root); +console.log('\n前順走査のノード出力シーケンス = ' + list); + +/* 中順走査 */ +list.length = 0; +inOrder(root); +console.log('\n中順走査のノード出力シーケンス = ' + list); + +/* 後順走査 */ +list.length = 0; +postOrder(root); +console.log('\n後順走査のノード出力シーケンス = ' + list); + +export {}; diff --git a/ja/codes/typescript/modules/ListNode.ts b/ja/codes/typescript/modules/ListNode.ts new file mode 100644 index 000000000..b2b3bc196 --- /dev/null +++ b/ja/codes/typescript/modules/ListNode.ts @@ -0,0 +1,28 @@ +/** + * File: ListNode.ts + * Created Time: 2022-12-10 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 連結リストノード */ +class ListNode { + val: number; + next: ListNode | null; + constructor(val?: number, next?: ListNode | null) { + this.val = val === undefined ? 0 : val; + this.next = next === undefined ? null : next; + } +} + +/* 配列をデシリアライズして連結リストに変換する */ +function arrToLinkedList(arr: number[]): ListNode | null { + const dum: ListNode = new ListNode(0); + let head = dum; + for (const val of arr) { + head.next = new ListNode(val); + head = head.next; + } + return dum.next; +} + +export { ListNode, arrToLinkedList }; diff --git a/ja/codes/typescript/modules/PrintUtil.ts b/ja/codes/typescript/modules/PrintUtil.ts new file mode 100644 index 000000000..712a965fb --- /dev/null +++ b/ja/codes/typescript/modules/PrintUtil.ts @@ -0,0 +1,93 @@ +/** + * File: PrintUtil.ts + * Created Time: 2022-12-13 + * Author: Justin (xiefahit@gmail.com) + */ + +import { ListNode } from './ListNode'; +import { TreeNode, arrToTree } from './TreeNode'; + +/* 連結リストを出力 */ +function printLinkedList(head: ListNode | null): void { + const list: string[] = []; + while (head !== null) { + list.push(head.val.toString()); + head = head.next; + } + console.log(list.join(' -> ')); +} + +class Trunk { + prev: Trunk | null; + str: string; + + constructor(prev: Trunk | null, str: string) { + this.prev = prev; + this.str = str; + } +} + +/** + * 二分木を出力 + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +function printTree(root: TreeNode | null) { + printTreeHelper(root, null, false); +} + +/* 二分木を出力 */ +function printTreeHelper( + root: TreeNode | null, + prev: Trunk | null, + isRight: boolean +) { + if (root === null) { + return; + } + + let prev_str = ' '; + const trunk = new Trunk(prev, prev_str); + + printTreeHelper(root.right, trunk, true); + + if (prev === null) { + trunk.str = '———'; + } else if (isRight) { + trunk.str = '/———'; + prev_str = ' |'; + } else { + trunk.str = '\\———'; + prev.str = prev_str; + } + + showTrunks(trunk); + console.log(' ' + root.val); + + if (prev) { + prev.str = prev_str; + } + trunk.str = ' |'; + + printTreeHelper(root.left, trunk, false); +} + +function showTrunks(p: Trunk | null) { + if (p === null) { + return; + } + + showTrunks(p.prev); + process.stdout.write(p.str); +} + +/* ヒープを出力 */ +function printHeap(arr: number[]): void { + console.log('ヒープの配列表現:'); + console.log(arr); + console.log('ヒープの木構造表現:'); + const root = arrToTree(arr); + printTree(root); +} + +export { printLinkedList, printTree, printHeap }; diff --git a/ja/codes/typescript/modules/TreeNode.ts b/ja/codes/typescript/modules/TreeNode.ts new file mode 100644 index 000000000..f0c052fa0 --- /dev/null +++ b/ja/codes/typescript/modules/TreeNode.ts @@ -0,0 +1,37 @@ +/** + * File: TreeNode.ts + * Created Time: 2022-12-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/* 二分木ノード */ +class TreeNode { + val: number; // ノード値 + height: number; // ノードの高さ + left: TreeNode | null; // 左の子ノードへのポインタ + right: TreeNode | null; // 右の子ノードへのポインタ + constructor( + val?: number, + height?: number, + left?: TreeNode | null, + right?: TreeNode | null + ) { + this.val = val === undefined ? 0 : val; + this.height = height === undefined ? 0 : height; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } +} + +/* 配列をデシリアライズして二分木に変換する */ +function arrToTree(arr: (number | null)[], i: number = 0): TreeNode | null { + if (i < 0 || i >= arr.length || arr[i] === null) { + return null; + } + let root = new TreeNode(arr[i]); + root.left = arrToTree(arr, 2 * i + 1); + root.right = arrToTree(arr, 2 * i + 2); + return root; +} + +export { TreeNode, arrToTree }; diff --git a/ja/codes/typescript/modules/Vertex.ts b/ja/codes/typescript/modules/Vertex.ts new file mode 100644 index 000000000..1cc7ca11a --- /dev/null +++ b/ja/codes/typescript/modules/Vertex.ts @@ -0,0 +1,33 @@ +/** + * File: Vertex.ts + * Created Time: 2023-02-15 + * Author: Zhuo Qinyue (1403450829@qq.com) + */ + +/* 頂点クラス */ +class Vertex { + val: number; + constructor(val: number) { + this.val = val; + } + + /* 値リスト vals を入力し、頂点リスト vets を返す */ + public static valsToVets(vals: number[]): Vertex[] { + const vets: Vertex[] = []; + for (let i = 0; i < vals.length; i++) { + vets[i] = new Vertex(vals[i]); + } + return vets; + } + + /* 頂点リスト vets を入力し、値リスト vals を返す */ + public static vetsToVals(vets: Vertex[]): number[] { + const vals: number[] = []; + for (const vet of vets) { + vals.push(vet.val); + } + return vals; + } +} + +export { Vertex }; diff --git a/ja/codes/typescript/package.json b/ja/codes/typescript/package.json new file mode 100644 index 000000000..ae81c64fe --- /dev/null +++ b/ja/codes/typescript/package.json @@ -0,0 +1,11 @@ +{ + "private": true, + "type": "module", + "scripts": { + "check": "tsc" + }, + "devDependencies": { + "@types/node": "^24.9.2", + "typescript": "^5.9.3" + } +} diff --git a/ja/codes/typescript/tsconfig.json b/ja/codes/typescript/tsconfig.json new file mode 100644 index 000000000..954efb8e7 --- /dev/null +++ b/ja/codes/typescript/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "module": "esnext", + "moduleResolution": "node", + "types": ["@types/node"], + "noEmit": true, + "target": "esnext", + }, + "include": ["chapter_*/*.ts"], + "exclude": ["node_modules"] +} diff --git a/ja/codes/zig/.gitignore b/ja/codes/zig/.gitignore new file mode 100644 index 000000000..76bab71be --- /dev/null +++ b/ja/codes/zig/.gitignore @@ -0,0 +1,4 @@ +zig-out +zig-cache +.zig-cache +!/.vscode/ \ No newline at end of file diff --git a/ja/codes/zig/.vscode/launch.json b/ja/codes/zig/.vscode/launch.json new file mode 100644 index 000000000..cfeec2388 --- /dev/null +++ b/ja/codes/zig/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/zig-out/bin/${fileBasenameNoExtension}", + "args": [], + "cwd": "${workspaceFolder}", + "preLaunchTask": "build" + } + ] +} \ No newline at end of file diff --git a/ja/codes/zig/.vscode/settings.json b/ja/codes/zig/.vscode/settings.json new file mode 100644 index 000000000..a1ccb9aa3 --- /dev/null +++ b/ja/codes/zig/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "zig.testArgs": ["build", "test", "-Dtest-filter=${filter}"] +} \ No newline at end of file diff --git a/ja/codes/zig/.vscode/tasks.json b/ja/codes/zig/.vscode/tasks.json new file mode 100644 index 000000000..a66ac10e2 --- /dev/null +++ b/ja/codes/zig/.vscode/tasks.json @@ -0,0 +1,10 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "type": "shell", + "command": "zig build", + } + ] +} \ No newline at end of file diff --git a/ja/codes/zig/build.zig b/ja/codes/zig/build.zig new file mode 100644 index 000000000..261ecd522 --- /dev/null +++ b/ja/codes/zig/build.zig @@ -0,0 +1,169 @@ +// File: build.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +//! Zig Version: 0.14.1 +//! Build Command: zig build +//! Run Command: zig build run | zig build run_* +//! Test Command: zig build test | zig build test -Dtest-filter=* + +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const chapters = [_][]const u8{ + "chapter_computational_complexity", + "chapter_array_and_linkedlist", + "chapter_stack_and_queue", + "chapter_hashing", + "chapter_tree", + "chapter_heap", + "chapter_searching", + "chapter_sorting", + "chapter_dynamic_programming", + }; + + const test_step = b.step("test", "Run unit tests"); + const test_filters = b.option([]const []const u8, "test-filter", "Skip tests that do not match any filter") orelse &[0][]const u8{}; + + buildChapterExeModules(b, target, optimize, &chapters, test_step, test_filters); + buildMainExeModule(b, target, optimize); +} + +fn buildChapterExeModules( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + chapter_dirs: []const []const u8, + test_step: *std.Build.Step, + test_filters: []const []const u8, +) void { + for (chapter_dirs) |chapter_dir_name| { + const chapter_dir_path = std.fs.path.join(b.allocator, &[_][]const u8{chapter_dir_name}) catch continue; + var chapter_dir = std.fs.cwd().openDir(chapter_dir_path, .{ .iterate = true }) catch continue; + defer chapter_dir.close(); + + var it = chapter_dir.iterate(); + while (it.next() catch continue) |chapter_dir_entry| { + if (chapter_dir_entry.kind != .file or !std.mem.endsWith(u8, chapter_dir_entry.name, ".zig")) continue; + const exe_mod = buildExeModuleFromChapterDirEntry(b, target, optimize, chapter_dir_name, chapter_dir_entry) catch continue; + addTestStepToExeModule(b, test_step, exe_mod, test_filters); + } + } +} + +fn buildExeModuleFromChapterDirEntry( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + chapter_dir_name: []const u8, + chapter_dir_entry: std.fs.Dir.Entry, +) !*std.Build.Module { + const zig_file_path = try std.fs.path.join(b.allocator, &[_][]const u8{ chapter_dir_name, chapter_dir_entry.name }); + const zig_file_name = chapter_dir_entry.name[0 .. chapter_dir_entry.name.len - 4]; // abstract zig file name from xxx.zig + + // ここでは一時的に配列と連結リストの章のみを追加し、後続の修正が終わったらすべて有効にする + const new_algo_names = [_][]const u8{ + "array", + "linked_list", + "list", + "my_list", + "iteration", + "recursion", + "space_complexity", + "time_complexity", + "worst_best_time_complexity", + }; + var can_run = false; + for (new_algo_names) |name| { + if (std.mem.eql(u8, zig_file_name, name)) { + can_run = true; + } + } + if (!can_run) { + return error.CanNotRunUseOldZigCodes; + } + + // std.debug.print("now run zig file name = {s}\n", .{zig_file_name}); + + const exe_mod = b.createModule(.{ + .root_source_file = b.path(zig_file_path), + .target = target, + .optimize = optimize, + }); + + const exe = b.addExecutable(.{ + .name = zig_file_name, + .root_module = exe_mod, + }); + + const utils_mod = createUtilsModule(b, target, optimize); + exe_mod.addImport("utils", utils_mod); + + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const step_name = try std.fmt.allocPrint(b.allocator, "run_{s}", .{zig_file_name}); + const step_desc = try std.fmt.allocPrint(b.allocator, "Run {s}/{s}.zig", .{ chapter_dir_name, zig_file_name }); + const run_step = b.step(step_name, step_desc); + run_step.dependOn(&run_cmd.step); + + return exe_mod; +} + +fn buildMainExeModule( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, +) void { + const exe_mod = b.createModule(.{ + .root_source_file = b.path("main.zig"), + .target = target, + .optimize = optimize, + }); + + const utils_mod = createUtilsModule(b, target, optimize); + exe_mod.addImport("utils", utils_mod); + + const exe = b.addExecutable(.{ + .name = "main", + .root_module = exe_mod, + }); + + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run all hello algo zig"); + run_step.dependOn(&run_cmd.step); +} + +fn createUtilsModule(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *std.Build.Module { + const utils_mod = b.createModule(.{ + .root_source_file = b.path("utils/utils.zig"), + .target = target, + .optimize = optimize, + }); + return utils_mod; +} + +fn addTestStepToExeModule(b: *std.Build, test_step: *std.Build.Step, exe_mod: *std.Build.Module, test_filters: []const []const u8) void { + const exe_unit_tests = b.addTest(.{ + .root_module = exe_mod, + .filters = test_filters, + }); + + const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); + test_step.dependOn(&run_exe_unit_tests.step); +} diff --git a/ja/codes/zig/chapter_array_and_linkedlist/array.zig b/ja/codes/zig/chapter_array_and_linkedlist/array.zig new file mode 100644 index 000000000..227e8ef12 --- /dev/null +++ b/ja/codes/zig/chapter_array_and_linkedlist/array.zig @@ -0,0 +1,131 @@ +// File: array.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); +const utils = @import("utils"); + +// 要素へランダムアクセス +pub fn randomAccess(nums: []const i32) i32 { + // 区間 [0, nums.len) からランダムに整数を 1 つ選ぶ + const random_index = std.crypto.random.intRangeLessThan(usize, 0, nums.len); + // ランダムな要素を取得して返す + const randomNum = nums[random_index]; + return randomNum; +} + +// 配列長を拡張する +pub fn extend(allocator: std.mem.Allocator, nums: []const i32, enlarge: usize) ![]i32 { + // 拡張後の長さを持つ配列を初期化する + const res = try allocator.alloc(i32, nums.len + enlarge); + @memset(res, 0); + + // 元の配列の全要素を新しい配列にコピー + std.mem.copyForwards(i32, res, nums); + + // 拡張後の新しい配列を返す + return res; +} + +// 配列の index 番目に要素 num を挿入 +pub fn insert(nums: []i32, num: i32, index: usize) void { + // インデックス index 以降の全要素を 1 つ後ろへ移動する + var i = nums.len - 1; + while (i > index) : (i -= 1) { + nums[i] = nums[i - 1]; + } + // index の要素に num を代入する + nums[index] = num; +} + +// index の要素を削除する +pub fn remove(nums: []i32, index: usize) void { + // インデックス index より後ろの全要素を 1 つ前へ移動する + var i = index; + while (i < nums.len - 1) : (i += 1) { + nums[i] = nums[i + 1]; + } +} + +// 配列を走査 +pub fn traverse(nums: []const i32) void { + var count: i32 = 0; + + // インデックスで配列を走査 + var i: usize = 0; + while (i < nums.len) : (i += 1) { + count += nums[i]; + } + + // 配列要素を直接走査 + count = 0; + for (nums) |num| { + count += num; + } + + // データのインデックスと要素を同時に走査する + for (nums, 0..) |num, index| { + count += nums[index]; + count += num; + } +} + +// 配列内で指定要素を探す +pub fn find(nums: []i32, target: i32) i32 { + for (nums, 0..) |num, i| { + if (num == target) return @intCast(i); + } + return -1; +} + +// Driver Code +pub fn run() !void { + // 配列を初期化 + const arr = [_]i32{0} ** 5; + std.debug.print("配列 arr = {}\n", .{utils.fmt.slice(&arr)}); + + // 配列スライス + var array = [_]i32{ 1, 3, 2, 5, 4 }; + var known_at_runtime_zero: usize = 0; + _ = &known_at_runtime_zero; + var nums = array[known_at_runtime_zero..array.len]; // 実行時変数 known_at_runtime_zero を用いてポインタをスライスに変換する + std.debug.print("配列 nums = {}\n", .{utils.fmt.slice(nums)}); + + // ランダムアクセス + const randomNum = randomAccess(nums); + std.debug.print("nums からランダムな要素 {} を取得\n", .{randomNum}); + + // メモリアロケータを初期化する + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + // 長さを拡張 + nums = try extend(allocator, nums, 3); + std.debug.print("配列の長さを 8 に拡張し、nums = {} を得る\n", .{utils.fmt.slice(nums)}); + + // 要素を挿入する + insert(nums, 6, 3); + std.debug.print("インデックス 3 に数値 6 を挿入し、nums = {} を得る\n", .{utils.fmt.slice(nums)}); + + // 要素を削除 + remove(nums, 2); + std.debug.print("インデックス 2 の要素を削除し、nums = {} を得る\n", .{utils.fmt.slice(nums)}); + + // 配列を走査 + traverse(nums); + + // 要素を探索する + const index = find(nums, 3); + std.debug.print("nums で要素 3 を検索し、インデックス = {} を得る\n", .{index}); + + std.debug.print("\n", .{}); +} + +pub fn main() !void { + try run(); +} + +test "array" { + try run(); +} diff --git a/ja/codes/zig/chapter_array_and_linkedlist/linked_list.zig b/ja/codes/zig/chapter_array_and_linkedlist/linked_list.zig new file mode 100644 index 000000000..55d1f9e28 --- /dev/null +++ b/ja/codes/zig/chapter_array_and_linkedlist/linked_list.zig @@ -0,0 +1,106 @@ +// File: linked_list.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); +const utils = @import("utils"); +const ListNode = utils.ListNode; + +// 連結リストでノード n0 の後ろにノード P を挿入する +pub fn insert(comptime T: type, n0: *ListNode(T), P: *ListNode(T)) void { + const n1 = n0.next; + P.next = n1; + n0.next = P; +} + +// 連結リストでノード n0 の直後のノードを削除する +pub fn remove(comptime T: type, n0: *ListNode(T)) void { + // n0 -> P -> n1 => n0 -> n1 + const P = n0.next; + const n1 = P.?.next; + n0.next = n1; +} + +// 連結リスト内で index 番目のノードにアクセス +pub fn access(comptime T: type, node: *ListNode(T), index: i32) ?*ListNode(T) { + var head: ?*ListNode(T) = node; + var i: i32 = 0; + while (i < index) : (i += 1) { + if (head) |cur| { + head = cur.next; + } else { + return null; + } + } + return head; +} + +// 連結リストで値が target の最初のノードを探す +pub fn find(comptime T: type, node: *ListNode(T), target: T) i32 { + var head: ?*ListNode(T) = node; + var index: i32 = 0; + while (head) |cur| { + if (cur.val == target) return index; + head = cur.next; + index += 1; + } + return -1; +} + +// Driver Code +pub fn run() void { + // 各ノードを初期化 + var n0 = ListNode(i32){ .val = 1 }; + var n1 = ListNode(i32){ .val = 3 }; + var n2 = ListNode(i32){ .val = 2 }; + var n3 = ListNode(i32){ .val = 5 }; + var n4 = ListNode(i32){ .val = 4 }; + // ノード間の参照を構築する + n0.next = &n1; + n1.next = &n2; + n2.next = &n3; + n3.next = &n4; + std.debug.print( + "初期化後の連結リストは {}\n", + .{utils.fmt.linkedList(i32, &n0)}, + ); + + // ノードを挿入 + var tmp = ListNode(i32){ .val = 0 }; + insert(i32, &n0, &tmp); + std.debug.print( + "ノード挿入後の連結リストは {}\n", + .{utils.fmt.linkedList(i32, &n0)}, + ); + + // ノードを削除 + remove(i32, &n0); + std.debug.print( + "ノード削除後の連結リストは{}\n", + .{utils.fmt.linkedList(i32, &n0)}, + ); + + // ノードにアクセス + const node = access(i32, &n0, 3); + std.debug.print( + "連結リストのインデックス 3 にあるノードの値 = {}\n", + .{node.?.val}, + ); + + // ノードを探索 + const index = find(i32, &n0, 2); + std.debug.print( + "連結リスト内の値 2 のノードのインデックス = {}\n", + .{index}, + ); + + std.debug.print("\n", .{}); +} + +pub fn main() void { + run(); +} + +test "linked_list" { + run(); +} diff --git a/ja/codes/zig/chapter_array_and_linkedlist/list.zig b/ja/codes/zig/chapter_array_and_linkedlist/list.zig new file mode 100644 index 000000000..cfc1866e4 --- /dev/null +++ b/ja/codes/zig/chapter_array_and_linkedlist/list.zig @@ -0,0 +1,78 @@ +// File: list.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); +const utils = @import("utils"); + +// Driver Code +pub fn run() !void { + // リストを初期化 + var nums = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums.deinit(); // メモリの遅延解放 + + try nums.appendSlice(&[_]i32{ 1, 3, 2, 5, 4 }); + std.debug.print("リスト nums = {}\n", .{utils.fmt.slice(nums.items)}); + + // 要素にアクセス + const num = nums.items[1]; + std.debug.print("インデックス 1 の要素にアクセスすると、num = {}\n", .{num}); + + // 要素を更新 + nums.items[1] = 0; + std.debug.print("インデックス 1 の要素を 0 に更新すると、nums = {}\n", .{utils.fmt.slice(nums.items)}); + + // リストを空にする + nums.clearRetainingCapacity(); + std.debug.print("リストを空にした後 nums = {}\n", .{utils.fmt.slice(nums.items)}); + + // 末尾に要素を追加 + try nums.append(1); + try nums.append(3); + try nums.append(2); + try nums.append(5); + try nums.append(4); + std.debug.print("要素を追加すると nums = {}\n", .{utils.fmt.slice(nums.items)}); + + // 中間に要素を挿入 + try nums.insert(3, 6); + std.debug.print("インデックス 3 に数値 6 を挿入すると、nums = {}\n", .{utils.fmt.slice(nums.items)}); + + // 要素を削除 + _ = nums.orderedRemove(3); + std.debug.print("インデックス 3 の要素を削除すると、nums = {}\n", .{utils.fmt.slice(nums.items)}); + + // インデックスでリストを走査 + var count: i32 = 0; + var i: usize = 0; + while (i < nums.items.len) : (i += 1) { + count += nums.items[i]; + } + + // リスト要素を直接走査 + count = 0; + for (nums.items) |x| { + count += x; + } + + // 2 つのリストを連結する + var nums1 = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums1.deinit(); + try nums1.appendSlice(&[_]i32{ 6, 8, 7, 10, 9 }); + try nums.insertSlice(nums.items.len, nums1.items); + std.debug.print("リスト nums1 を nums の後ろに連結すると、nums = {}\n", .{utils.fmt.slice(nums.items)}); + + // リストをソート + std.mem.sort(i32, nums.items, {}, comptime std.sort.asc(i32)); + std.debug.print("リストをソートすると nums = {}\n", .{utils.fmt.slice(nums.items)}); + + std.debug.print("\n", .{}); +} + +pub fn main() !void { + try run(); +} + +test "list" { + try run(); +} diff --git a/ja/codes/zig/chapter_array_and_linkedlist/my_list.zig b/ja/codes/zig/chapter_array_and_linkedlist/my_list.zig new file mode 100644 index 000000000..a69145e91 --- /dev/null +++ b/ja/codes/zig/chapter_array_and_linkedlist/my_list.zig @@ -0,0 +1,233 @@ +// File: my_list.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); +const utils = @import("utils"); + +// リストクラス +const MyList = struct { + const Self = @This(); + + items: []i32, // 配列(リスト要素を格納) + capacity: usize, // リスト容量 + allocator: std.mem.Allocator, // メモリアロケータ + + extend_ratio: usize = 2, // リスト拡張時の増加倍率 + + // コンストラクタ(メモリを確保してリストを初期化) + pub fn init(allocator: std.mem.Allocator) Self { + return Self{ + .items = &[_]i32{}, + .capacity = 0, + .allocator = allocator, + }; + } + + // デストラクタ(メモリを解放する) + pub fn deinit(self: Self) void { + self.allocator.free(self.allocatedSlice()); + } + + // 末尾に要素を追加 + pub fn add(self: *Self, item: i32) !void { + // 要素数が容量を超えると、拡張機構が発動する + const newlen = self.items.len + 1; + try self.ensureTotalCapacity(newlen); + + // 要素を更新 + self.items.len += 1; + const new_item_ptr = &self.items[self.items.len - 1]; + new_item_ptr.* = item; + } + + // リストの長さを取得(現在の要素数) + pub fn getSize(self: *Self) usize { + return self.items.len; + } + + // リスト容量を取得する + pub fn getCapacity(self: *Self) usize { + return self.capacity; + } + + // 要素にアクセス + pub fn get(self: *Self, index: usize) i32 { + // インデックスが範囲外なら例外を送出する。以下同様 + if (index < 0 or index >= self.items.len) { + @panic("インデックスが範囲外です"); + } + return self.items[index]; + } + + // 要素を更新 + pub fn set(self: *Self, index: usize, num: i32) void { + // インデックスが範囲外なら例外を送出する。以下同様 + if (index < 0 or index >= self.items.len) { + @panic("インデックスが範囲外です"); + } + self.items[index] = num; + } + + // 中間に要素を挿入 + pub fn insert(self: *Self, index: usize, item: i32) !void { + if (index < 0 or index >= self.items.len) { + @panic("インデックスが範囲外です"); + } + + // 要素数が容量を超えると、拡張機構が発動する + const newlen = self.items.len + 1; + try self.ensureTotalCapacity(newlen); + + // index 以降の要素をすべて 1 つ後ろへずらす + self.items.len += 1; + var i = self.items.len - 1; + while (i >= index) : (i -= 1) { + self.items[i] = self.items[i - 1]; + } + self.items[index] = item; + } + + // 要素を削除 + pub fn remove(self: *Self, index: usize) i32 { + if (index < 0 or index >= self.getSize()) { + @panic("インデックスが範囲外です"); + } + // インデックス index より後の要素をすべて 1 つ前に移動する + const item = self.items[index]; + var i = index; + while (i < self.items.len - 1) : (i += 1) { + self.items[i] = self.items[i + 1]; + } + self.items.len -= 1; + // 削除された要素を返す + return item; + } + + // リストを配列に変換する + pub fn toArraySlice(self: *Self) ![]i32 { + return self.toOwnedSlice(false); + } + + // 新しいスライスを返し、リストコンテナをリセットまたはクリアするかどうかを設定する + pub fn toOwnedSlice(self: *Self, clear: bool) ![]i32 { + const allocator = self.allocator; + const old_memory = self.allocatedSlice(); + if (allocator.remap(old_memory, self.items.len)) |new_items| { + if (clear) { + self.* = init(allocator); + } + return new_items; + } + + const new_memory = try allocator.alloc(i32, self.items.len); + @memcpy(new_memory, self.items); + if (clear) { + self.clearAndFree(); + } + return new_memory; + } + + // リストの拡張 + fn ensureTotalCapacity(self: *Self, new_capacity: usize) !void { + if (self.capacity >= new_capacity) return; + const capcacity = if (self.capacity == 0) 10 else self.capacity; + const better_capacity = capcacity * self.extend_ratio; + + const old_memory = self.allocatedSlice(); + if (self.allocator.remap(old_memory, better_capacity)) |new_memory| { + self.items.ptr = new_memory.ptr; + self.capacity = new_memory.len; + } else { + const new_memory = try self.allocator.alloc(i32, better_capacity); + @memcpy(new_memory[0..self.items.len], self.items); + self.allocator.free(old_memory); + self.items.ptr = new_memory.ptr; + self.capacity = new_memory.len; + } + } + + fn clearAndFree(self: *Self, allocator: std.mem.Allocator) void { + allocator.free(self.allocatedSlice()); + self.items.len = 0; + self.capacity = 0; + } + + fn allocatedSlice(self: Self) []i32 { + return self.items.ptr[0..self.capacity]; + } +}; + +// Driver Code +pub fn run() !void { + var gpa = std.heap.DebugAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + // リストを初期化 + var nums = MyList.init(allocator); + // メモリの遅延解放 + defer nums.deinit(); + + // 末尾に要素を追加 + try nums.add(1); + try nums.add(3); + try nums.add(2); + try nums.add(5); + try nums.add(4); + std.debug.print("リスト nums = {} ,容量 = {} ,長さ = {}\n", .{ + utils.fmt.slice(nums.items), + nums.getCapacity(), + nums.getSize(), + }); + + // 中間に要素を挿入 + try nums.insert(3, 6); + std.debug.print( + "インデックス 3 に数値 6 を挿入すると、nums = {}\n", + .{utils.fmt.slice(nums.items)}, + ); + + // 要素を削除 + _ = nums.remove(3); + std.debug.print( + "インデックス 3 の要素を削除すると、nums = {}\n", + .{utils.fmt.slice(nums.items)}, + ); + + // 要素にアクセス + const num = nums.get(1); + std.debug.print("インデックス 1 の要素にアクセスすると、num = {}\n", .{num}); + + // 要素を更新 + nums.set(1, 0); + std.debug.print( + "インデックス 1 の要素を 0 に更新すると、nums = {}\n", + .{utils.fmt.slice(nums.items)}, + ); + + // 拡張機構をテストする + var i: i32 = 0; + while (i < 10) : (i += 1) { + // i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する + try nums.add(i); + } + std.debug.print( + "拡張後のリスト nums = {} ,容量 = {} ,長さ = {}\n", + .{ + utils.fmt.slice(nums.items), + nums.getCapacity(), + nums.getSize(), + }, + ); + + std.debug.print("\n", .{}); +} + +pub fn main() !void { + try run(); +} + +test "my_list" { + try run(); +} diff --git a/ja/codes/zig/chapter_computational_complexity/iteration.zig b/ja/codes/zig/chapter_computational_complexity/iteration.zig new file mode 100644 index 000000000..9b9f80f97 --- /dev/null +++ b/ja/codes/zig/chapter_computational_complexity/iteration.zig @@ -0,0 +1,91 @@ +// File: iteration.zig +// Created Time: 2023-09-27 +// Author: QiLOL (pikaqqpika@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); +const Allocator = std.mem.Allocator; + +// for ループ +fn forLoop(n: usize) i32 { + var res: i32 = 0; + // 1, 2, ..., n-1, n を順に加算する + for (1..n + 1) |i| { + res += @intCast(i); + } + return res; +} + +// while ループ +fn whileLoop(n: i32) i32 { + var res: i32 = 0; + var i: i32 = 1; // 条件変数を初期化する + // 1, 2, ..., n-1, n を順に加算する + while (i <= n) : (i += 1) { + res += @intCast(i); + } + return res; +} + +// while ループ(2回更新) +fn whileLoopII(n: i32) i32 { + var res: i32 = 0; + var i: i32 = 1; // 条件変数を初期化する + // 1, 4, 10, ... を順に加算する + while (i <= n) : ({ + // 条件変数を更新する + i += 1; + i *= 2; + }) { + res += @intCast(i); + } + return res; +} + +// 二重 for ループ +fn nestedForLoop(allocator: Allocator, n: usize) ![]const u8 { + var res = std.ArrayList(u8).init(allocator); + defer res.deinit(); + var buffer: [20]u8 = undefined; + // i = 1, 2, ..., n-1, n とループする + for (1..n + 1) |i| { + // j = 1, 2, ..., n-1, n とループする + for (1..n + 1) |j| { + const str = try std.fmt.bufPrint(&buffer, "({d}, {d}), ", .{ i, j }); + try res.appendSlice(str); + } + } + return res.toOwnedSlice(); +} + +// Driver Code +pub fn run() !void { + var gpa = std.heap.DebugAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const n: i32 = 5; + var res: i32 = 0; + + res = forLoop(n); + std.debug.print("for ループの合計結果 res = {}\n", .{res}); + + res = whileLoop(n); + std.debug.print("while ループの合計結果 res = {}\n", .{res}); + + res = whileLoopII(n); + std.debug.print("while ループ(2 回更新)の合計結果 res = {}\n", .{res}); + + const resStr = try nestedForLoop(allocator, n); + std.debug.print("二重 for ループの走査結果 {s}\n", .{resStr}); + allocator.free(resStr); + + std.debug.print("\n", .{}); +} + +pub fn main() !void { + try run(); +} + +test "interation" { + try run(); +} diff --git a/ja/codes/zig/chapter_computational_complexity/recursion.zig b/ja/codes/zig/chapter_computational_complexity/recursion.zig new file mode 100644 index 000000000..a73d020b6 --- /dev/null +++ b/ja/codes/zig/chapter_computational_complexity/recursion.zig @@ -0,0 +1,88 @@ +// File: recursion.zig +// Created Time: 2023-09-27 +// Author: QiLOL (pikaqqpika@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); + +// 再帰関数 +fn recur(n: i32) i32 { + // 終了条件 + if (n == 1) { + return 1; + } + // 再帰:再帰呼び出し + const res = recur(n - 1); + // 帰りがけ:結果を返す + return n + res; +} + +// 反復で再帰を模擬する +fn forLoopRecur(comptime n: i32) i32 { + // 明示的なスタックを使ってシステムコールスタックを模擬する + var stack: [n]i32 = undefined; + var res: i32 = 0; + // 再帰:再帰呼び出し + var i: usize = n; + while (i > 0) { + stack[i - 1] = @intCast(i); + i -= 1; + } + // 帰りがけ:結果を返す + var index: usize = n; + while (index > 0) { + index -= 1; + res += stack[index]; + } + // res = 1+2+3+...+n + return res; +} + +// 末尾再帰関数 +fn tailRecur(n: i32, res: i32) i32 { + // 終了条件 + if (n == 0) { + return res; + } + // 末尾再帰呼び出し + return tailRecur(n - 1, res + n); +} + +// フィボナッチ数列 +fn fib(n: i32) i32 { + // 終了条件 f(1) = 0, f(2) = 1 + if (n == 1 or n == 2) { + return n - 1; + } + // f(n) = f(n-1) + f(n-2) を再帰的に呼び出す + const res: i32 = fib(n - 1) + fib(n - 2); + // 結果 f(n) を返す + return res; +} + +// Driver Code +pub fn run() void { + const n: i32 = 5; + var res: i32 = 0; + + res = recur(n); + std.debug.print("再帰関数の合計結果 res = {}\n", .{recur(n)}); + + res = forLoopRecur(n); + std.debug.print("反復で再帰をシミュレートした合計結果 res = {}\n", .{forLoopRecur(n)}); + + res = tailRecur(n, 0); + std.debug.print("末尾再帰関数の合計結果 res = {}\n", .{tailRecur(n, 0)}); + + res = fib(n); + std.debug.print("フィボナッチ数列の第 {} 項は {}\n", .{ n, fib(n) }); + + std.debug.print("\n", .{}); +} + +pub fn main() void { + run(); +} + +test "recursion" { + run(); +} diff --git a/ja/codes/zig/chapter_computational_complexity/space_complexity.zig b/ja/codes/zig/chapter_computational_complexity/space_complexity.zig new file mode 100644 index 000000000..af0251f9a --- /dev/null +++ b/ja/codes/zig/chapter_computational_complexity/space_complexity.zig @@ -0,0 +1,142 @@ +// File: space_complexity.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); +const utils = @import("utils"); +const ListNode = utils.ListNode; +const TreeNode = utils.TreeNode; + +// 関数 +fn function() i32 { + // 何らかの処理を行う + return 0; +} + +// 定数階 +fn constant(n: i32) void { + // 定数、変数、オブジェクトは O(1) の空間を占める + const a: i32 = 0; + const b: i32 = 0; + const nums = [_]i32{0} ** 10000; + const node = ListNode(i32){ .val = 0 }; + var i: i32 = 0; + // ループ内の変数は O(1) の空間を占める + while (i < n) : (i += 1) { + const c: i32 = 0; + _ = c; + } + // ループ内の関数は O(1) の空間を占める + i = 0; + while (i < n) : (i += 1) { + _ = function(); + } + _ = a; + _ = b; + _ = nums; + _ = node; +} + +// 線形階 +fn linear(comptime n: i32) !void { + // 長さ n の配列は O(n) の空間を使用 + const nums = [_]i32{0} ** n; + // 長さ n のリストは O(n) の空間を使用 + var nodes = std.ArrayList(i32).init(std.heap.page_allocator); + defer nodes.deinit(); + var i: i32 = 0; + while (i < n) : (i += 1) { + try nodes.append(i); + } + // 長さ n のハッシュテーブルは O(n) の空間を使用 + var map = std.AutoArrayHashMap(i32, []const u8).init(std.heap.page_allocator); + defer map.deinit(); + var j: i32 = 0; + while (j < n) : (j += 1) { + const string = try std.fmt.allocPrint(std.heap.page_allocator, "{d}", .{j}); + defer std.heap.page_allocator.free(string); + try map.put(i, string); + } + _ = nums; +} + +// 線形時間(再帰実装) +fn linearRecur(comptime n: i32) void { + std.debug.print("再帰 n = {}\n", .{n}); + if (n == 1) return; + linearRecur(n - 1); +} + +// 二乗階 +fn quadratic(n: i32) !void { + // 二次元リストは O(n^2) の空間を使用 + var nodes = std.ArrayList(std.ArrayList(i32)).init(std.heap.page_allocator); + defer nodes.deinit(); + var i: i32 = 0; + while (i < n) : (i += 1) { + var tmp = std.ArrayList(i32).init(std.heap.page_allocator); + defer tmp.deinit(); + var j: i32 = 0; + while (j < n) : (j += 1) { + try tmp.append(0); + } + try nodes.append(tmp); + } +} + +// 二次時間(再帰実装) +fn quadraticRecur(comptime n: i32) i32 { + if (n <= 0) return 0; + const nums = [_]i32{0} ** n; + std.debug.print("再帰 n = {} における nums の長さ = {}\n", .{ n, nums.len }); + return quadraticRecur(n - 1); +} + +// 指数時間(完全二分木の構築) +fn buildTree(allocator: std.mem.Allocator, n: i32) !?*TreeNode(i32) { + if (n == 0) return null; + const root = try allocator.create(TreeNode(i32)); + root.init(0); + root.left = try buildTree(allocator, n - 1); + root.right = try buildTree(allocator, n - 1); + return root; +} + +// 木のメモリを解放する +fn freeTree(allocator: std.mem.Allocator, root: ?*const TreeNode(i32)) void { + if (root == null) return; + freeTree(allocator, root.?.left); + freeTree(allocator, root.?.right); + allocator.destroy(root.?); +} + +// Driver Code +pub fn run() !void { + var gpa = std.heap.DebugAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const n: i32 = 5; + // 定数階 + constant(n); + // 線形階 + try linear(n); + linearRecur(n); + // 二乗階 + try quadratic(n); + _ = quadraticRecur(n); + // 指数オーダー + const root = try buildTree(allocator, n); + defer freeTree(allocator, root); + std.debug.print("{}\n", .{utils.fmt.tree(i32, root)}); + + std.debug.print("\n", .{}); +} + +pub fn main() !void { + try run(); +} + +test "space_complexity" { + try run(); +} diff --git a/ja/codes/zig/chapter_computational_complexity/time_complexity.zig b/ja/codes/zig/chapter_computational_complexity/time_complexity.zig new file mode 100644 index 000000000..0a6c70840 --- /dev/null +++ b/ja/codes/zig/chapter_computational_complexity/time_complexity.zig @@ -0,0 +1,184 @@ +// File: time_complexity.zig +// Created Time: 2022-12-28 +// Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); + +// 定数階 +fn constant(n: i32) i32 { + _ = n; + var count: i32 = 0; + const size: i32 = 100_000; + var i: i32 = 0; + while (i < size) : (i += 1) { + count += 1; + } + return count; +} + +// 線形階 +fn linear(n: i32) i32 { + var count: i32 = 0; + var i: i32 = 0; + while (i < n) : (i += 1) { + count += 1; + } + return count; +} + +// 線形時間(配列を走査) +fn arrayTraversal(nums: []i32) i32 { + var count: i32 = 0; + // ループ回数は配列長に比例する + for (nums) |_| { + count += 1; + } + return count; +} + +// 二乗階 +fn quadratic(n: i32) i32 { + var count: i32 = 0; + var i: i32 = 0; + // ループ回数はデータサイズ n の二乗に比例する + while (i < n) : (i += 1) { + var j: i32 = 0; + while (j < n) : (j += 1) { + count += 1; + } + } + return count; +} + +// 二次時間(バブルソート) +fn bubbleSort(nums: []i32) i32 { + var count: i32 = 0; // カウンタ + // 外側のループ:未ソート区間は [0, i] + var i: i32 = @as(i32, @intCast(nums.len)) - 1; + while (i > 0) : (i -= 1) { + var j: usize = 0; + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + while (j < i) : (j += 1) { + if (nums[j] > nums[j + 1]) { + // nums[j] と nums[j + 1] を交換 + const tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 要素交換には 3 回の単位操作が含まれる + } + } + } + return count; +} + +// 指数時間(ループ実装) +fn exponential(n: i32) i32 { + var count: i32 = 0; + var bas: i32 = 1; + var i: i32 = 0; + // 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する + while (i < n) : (i += 1) { + var j: i32 = 0; + while (j < bas) : (j += 1) { + count += 1; + } + bas *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; +} + +// 指数時間(再帰実装) +fn expRecur(n: i32) i32 { + if (n == 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; +} + +// 対数時間(ループ実装) +fn logarithmic(n: i32) i32 { + var count: i32 = 0; + var n_var: i32 = n; + while (n_var > 1) : (n_var = @divTrunc(n_var, 2)) { + count += 1; + } + return count; +} + +// 対数時間(再帰実装) +fn logRecur(n: i32) i32 { + if (n <= 1) return 0; + return logRecur(@divTrunc(n, 2)) + 1; +} + +// 線形対数時間 +fn linearLogRecur(n: i32) i32 { + if (n <= 1) return 1; + var count: i32 = linearLogRecur(@divTrunc(n, 2)) + linearLogRecur(@divTrunc(n, 2)); + var i: i32 = 0; + while (i < n) : (i += 1) { + count += 1; + } + return count; +} + +// 階乗時間(再帰実装) +fn factorialRecur(n: i32) i32 { + if (n == 0) return 1; + var count: i32 = 0; + var i: i32 = 0; + // 1個から n 個に分裂 + while (i < n) : (i += 1) { + count += factorialRecur(n - 1); + } + return count; +} + +// Driver Code +pub fn run() void { + // n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる + const n: i32 = 8; + std.debug.print("入力データサイズ n = {}\n", .{n}); + + var count = constant(n); + std.debug.print("定数オーダーの操作数 = {}\n", .{count}); + + count = linear(n); + std.debug.print("線形オーダーの操作数 = {}\n", .{count}); + var nums = [_]i32{0} ** n; + count = arrayTraversal(&nums); + std.debug.print("線形オーダー(配列走査)の操作数 = {}\n", .{count}); + + count = quadratic(n); + std.debug.print("二乗オーダーの操作数 = {}\n", .{count}); + for (&nums, 0..) |*num, i| { + num.* = n - @as(i32, @intCast(i)); // [n,n-1,...,2,1] + } + count = bubbleSort(&nums); + std.debug.print("二乗オーダー(バブルソート)の操作数 = {}\n", .{count}); + + count = exponential(n); + std.debug.print("指数オーダー(ループ実装)の操作数 = {}\n", .{count}); + count = expRecur(n); + std.debug.print("指数オーダー(再帰実装)の操作数 = {}\n", .{count}); + + count = logarithmic(n); + std.debug.print("対数オーダー(ループ実装)の操作数 = {}\n", .{count}); + count = logRecur(n); + std.debug.print("対数オーダー(再帰実装)の操作数 = {}\n", .{count}); + + count = linearLogRecur(n); + std.debug.print("線形対数オーダー(再帰実装)の操作数 = {}\n", .{count}); + + count = factorialRecur(n); + std.debug.print("階乗オーダー(再帰実装)の操作数 = {}\n", .{count}); + + std.debug.print("\n", .{}); +} + +pub fn main() !void { + run(); +} + +test "time_complexity" { + run(); +} diff --git a/ja/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig b/ja/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig new file mode 100644 index 000000000..9f87229a3 --- /dev/null +++ b/ja/codes/zig/chapter_computational_complexity/worst_best_time_complexity.zig @@ -0,0 +1,53 @@ +// File: worst_best_time_complexity.zig +// Created Time: 2022-12-28 +// Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); +const utils = @import("utils"); + +// 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 +pub fn randomNumbers(comptime n: usize) [n]i32 { + var nums: [n]i32 = undefined; + // 配列 nums = { 1, 2, 3, ..., n } を生成 + for (&nums, 0..) |*num, i| { + num.* = @as(i32, @intCast(i)) + 1; + } + // 配列要素をランダムにシャッフル + const rand = std.crypto.random; + rand.shuffle(i32, &nums); + return nums; +} + +// 配列 nums 内で数値 1 のインデックスを探す +pub fn findOne(nums: []i32) i32 { + for (nums, 0..) |num, i| { + // 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる + // 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる + if (num == 1) return @intCast(i); + } + return -1; +} + +// Driver Code +pub fn run() void { + var i: i32 = 0; + while (i < 10) : (i += 1) { + const n: usize = 100; + var nums = randomNumbers(n); + const index = findOne(&nums); + std.debug.print("配列 [ 1, 2, ..., n ] をシャッフルした後 = ", .{}); + std.debug.print("{}\n", .{utils.fmt.slice(nums)}); + + std.debug.print("数字 1 のインデックスは {}\n", .{index}); + } + + std.debug.print("\n", .{}); +} + +pub fn main() !void { + run(); +} + +test "worst_best_time_complexity" { + run(); +} diff --git a/ja/codes/zig/chapter_dynamic_programming/climbing_stairs_backtrack.zig b/ja/codes/zig/chapter_dynamic_programming/climbing_stairs_backtrack.zig new file mode 100644 index 000000000..994e8a600 --- /dev/null +++ b/ja/codes/zig/chapter_dynamic_programming/climbing_stairs_backtrack.zig @@ -0,0 +1,44 @@ +// File: climbing_stairs_backtrack.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// バックトラッキング +fn backtrack(choices: []i32, state: i32, n: i32, res: std.ArrayList(i32)) void { + // 第 n 段に到達したら、方法数を 1 増やす + if (state == n) { + res.items[0] = res.items[0] + 1; + } + // すべての選択肢を走査 + for (choices) |choice| { + // 枝刈り: 第 n 段を超えないようにする + if (state + choice > n) { + continue; + } + // 試行: 選択を行い、状態を更新 + backtrack(choices, state + choice, n, res); + // バックトラック + } +} + +// 階段登り:バックトラッキング +fn climbingStairsBacktrack(n: usize) !i32 { + var choices = [_]i32{ 1, 2 }; // 1 段または 2 段上ることを選べる + var state: i32 = 0; // 第 0 段から上り始める + var res = std.ArrayList(i32).init(std.heap.page_allocator); + defer res.deinit(); + try res.append(0); // res[0] を使って方法数を記録する + backtrack(&choices, state, @intCast(n), res); + return res.items[0]; +} + +// Driver Code +pub fn main() !void { + var n: usize = 9; + + var res = try climbingStairsBacktrack(n); + std.debug.print("{} 段の階段を上る方法は全部で {} 通り\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_dynamic_programming/climbing_stairs_constraint_dp.zig b/ja/codes/zig/chapter_dynamic_programming/climbing_stairs_constraint_dp.zig new file mode 100644 index 000000000..53dd700f5 --- /dev/null +++ b/ja/codes/zig/chapter_dynamic_programming/climbing_stairs_constraint_dp.zig @@ -0,0 +1,35 @@ +// File: climbing_stairs_constraint_dp.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 制約付き階段登り:動的計画法 +fn climbingStairsConstraintDP(comptime n: usize) i32 { + if (n == 1 or n == 2) { + return 1; + } + // 部分問題の解を保存するために dp テーブルを初期化 + var dp = [_][3]i32{ [_]i32{ -1, -1, -1 } } ** (n + 1); + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1][1] = 1; + dp[1][2] = 0; + dp[2][1] = 0; + dp[2][2] = 1; + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for (3..n + 1) |i| { + dp[i][1] = dp[i - 1][2]; + dp[i][2] = dp[i - 2][1] + dp[i - 2][2]; + } + return dp[n][1] + dp[n][2]; +} + +// Driver Code +pub fn main() !void { + comptime var n: usize = 9; + + var res = climbingStairsConstraintDP(n); + std.debug.print("{} 段の階段を上る方法は全部で {} 通り\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs.zig b/ja/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs.zig new file mode 100644 index 000000000..118c0fab6 --- /dev/null +++ b/ja/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs.zig @@ -0,0 +1,31 @@ +// File: climbing_stairs_dfs.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 検索 +fn dfs(i: usize) i32 { + // dp[1] と dp[2] は既知なので返す + if (i == 1 or i == 2) { + return @intCast(i); + } + // dp[i] = dp[i-1] + dp[i-2] + var count = dfs(i - 1) + dfs(i - 2); + return count; +} + +// 階段登り:探索 +fn climbingStairsDFS(comptime n: usize) i32 { + return dfs(n); +} + +// Driver Code +pub fn main() !void { + comptime var n: usize = 9; + + var res = climbingStairsDFS(n); + std.debug.print("{} 段の階段を上る方法は全部で {} 通り\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs_mem.zig b/ja/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs_mem.zig new file mode 100644 index 000000000..ef30c1fe7 --- /dev/null +++ b/ja/codes/zig/chapter_dynamic_programming/climbing_stairs_dfs_mem.zig @@ -0,0 +1,39 @@ +// File: climbing_stairs_dfs_mem.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// メモ化探索 +fn dfs(i: usize, mem: []i32) i32 { + // dp[1] と dp[2] は既知なので返す + if (i == 1 or i == 2) { + return @intCast(i); + } + // dp[i] の記録があれば、それをそのまま返す + if (mem[i] != -1) { + return mem[i]; + } + // dp[i] = dp[i-1] + dp[i-2] + var count = dfs(i - 1, mem) + dfs(i - 2, mem); + // dp[i] を記録する + mem[i] = count; + return count; +} + +// 階段登り:メモ化探索 +fn climbingStairsDFSMem(comptime n: usize) i32 { + // mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す + var mem = [_]i32{ -1 } ** (n + 1); + return dfs(n, &mem); +} + +// Driver Code +pub fn main() !void { + comptime var n: usize = 9; + + var res = climbingStairsDFSMem(n); + std.debug.print("{} 段の階段を上る方法は全部で {} 通り\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_dynamic_programming/climbing_stairs_dp.zig b/ja/codes/zig/chapter_dynamic_programming/climbing_stairs_dp.zig new file mode 100644 index 000000000..a0ac006d4 --- /dev/null +++ b/ja/codes/zig/chapter_dynamic_programming/climbing_stairs_dp.zig @@ -0,0 +1,51 @@ +// File: climbing_stairs_dp.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 階段登り:動的計画法 +fn climbingStairsDP(comptime n: usize) i32 { + // dp[1] と dp[2] は既知なので返す + if (n == 1 or n == 2) { + return @intCast(n); + } + // 部分問題の解を保存するために dp テーブルを初期化 + var dp = [_]i32{-1} ** (n + 1); + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1] = 1; + dp[2] = 2; + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for (3..n + 1) |i| { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} + +// 階段登り:空間最適化した動的計画法 +fn climbingStairsDPComp(comptime n: usize) i32 { + if (n == 1 or n == 2) { + return @intCast(n); + } + var a: i32 = 1; + var b: i32 = 2; + for (3..n + 1) |_| { + var tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +// Driver Code +pub fn main() !void { + comptime var n: usize = 9; + + var res = climbingStairsDP(n); + std.debug.print("{} 段の階段を上る方法は全部で {} 通り\n", .{ n, res }); + + res = climbingStairsDPComp(n); + std.debug.print("{} 段の階段を上る方法は全部で {} 通り\n", .{ n, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_dynamic_programming/coin_change.zig b/ja/codes/zig/chapter_dynamic_programming/coin_change.zig new file mode 100644 index 000000000..07adb832b --- /dev/null +++ b/ja/codes/zig/chapter_dynamic_programming/coin_change.zig @@ -0,0 +1,77 @@ +// File: coin_change.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// コイン両替:動的計画法 +fn coinChangeDP(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + comptime var max = amt + 1; + // dp テーブルを初期化 + var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); + // 状態遷移:先頭行と先頭列 + for (1..amt + 1) |a| { + dp[0][a] = max; + } + // 状態遷移: 残りの行と列 + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[i][a] = dp[i - 1][a]; + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[i][a] = @min(dp[i - 1][a], dp[i][a - @as(usize, @intCast(coins[i - 1]))] + 1); + } + } + } + if (dp[n][amt] != max) { + return @intCast(dp[n][amt]); + } else { + return -1; + } +} + +// コイン交換:空間最適化後の動的計画法 +fn coinChangeDPComp(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + comptime var max = amt + 1; + // dp テーブルを初期化 + var dp = [_]i32{0} ** (amt + 1); + @memset(&dp, max); + dp[0] = 0; + // 状態遷移 + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a]; + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[a] = @min(dp[a], dp[a - @as(usize, @intCast(coins[i - 1]))] + 1); + } + } + } + if (dp[amt] != max) { + return @intCast(dp[amt]); + } else { + return -1; + } +} + +// Driver Code +pub fn main() !void { + comptime var coins = [_]i32{ 1, 2, 5 }; + comptime var amt: usize = 4; + + // 動的計画法 + var res = coinChangeDP(&coins, amt); + std.debug.print("目標金額にするために必要な最小の硬貨枚数は {}\n", .{res}); + + // 空間最適化後の動的計画法 + res = coinChangeDPComp(&coins, amt); + std.debug.print("目標金額にするために必要な最小の硬貨枚数は {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_dynamic_programming/coin_change_ii.zig b/ja/codes/zig/chapter_dynamic_programming/coin_change_ii.zig new file mode 100644 index 000000000..9df93dddf --- /dev/null +++ b/ja/codes/zig/chapter_dynamic_programming/coin_change_ii.zig @@ -0,0 +1,66 @@ +// File: coin_change_ii.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// コイン両替 II:動的計画法 +fn coinChangeIIDP(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + // dp テーブルを初期化 + var dp = [_][amt + 1]i32{[_]i32{0} ** (amt + 1)} ** (n + 1); + // 先頭列を初期化する + for (0..n + 1) |i| { + dp[i][0] = 1; + } + // 状態遷移 + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[i][a] = dp[i - 1][a]; + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[i][a] = dp[i - 1][a] + dp[i][a - @as(usize, @intCast(coins[i - 1]))]; + } + } + } + return dp[n][amt]; +} + +// コイン両替 II:空間最適化した動的計画法 +fn coinChangeIIDPComp(comptime coins: []i32, comptime amt: usize) i32 { + comptime var n = coins.len; + // dp テーブルを初期化 + var dp = [_]i32{0} ** (amt + 1); + dp[0] = 1; + // 状態遷移 + for (1..n + 1) |i| { + for (1..amt + 1) |a| { + if (coins[i - 1] > @as(i32, @intCast(a))) { + // 目標金額を超えるなら硬貨 i は選ばない + dp[a] = dp[a]; + } else { + // 硬貨 i を選ばない場合と選ぶ場合の小さい方 + dp[a] = dp[a] + dp[a - @as(usize, @intCast(coins[i - 1]))]; + } + } + } + return dp[amt]; +} + +// Driver Code +pub fn main() !void { + comptime var coins = [_]i32{ 1, 2, 5 }; + comptime var amt: usize = 5; + + // 動的計画法 + var res = coinChangeIIDP(&coins, amt); + std.debug.print("目標金額を作る硬貨の組合せ数は {}\n", .{res}); + + // 空間最適化後の動的計画法 + res = coinChangeIIDPComp(&coins, amt); + std.debug.print("目標金額を作る硬貨の組合せ数は {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_dynamic_programming/edit_distance.zig b/ja/codes/zig/chapter_dynamic_programming/edit_distance.zig new file mode 100644 index 000000000..208bf1bfa --- /dev/null +++ b/ja/codes/zig/chapter_dynamic_programming/edit_distance.zig @@ -0,0 +1,146 @@ +// File: edit_distance.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 編集距離:総当たり探索 +fn editDistanceDFS(comptime s: []const u8, comptime t: []const u8, i: usize, j: usize) i32 { + // s と t がともに空なら 0 を返す + if (i == 0 and j == 0) { + return 0; + } + // s が空なら t の長さを返す + if (i == 0) { + return @intCast(j); + } + // t が空なら s の長さを返す + if (j == 0) { + return @intCast(i); + } + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if (s[i - 1] == t[j - 1]) { + return editDistanceDFS(s, t, i - 1, j - 1); + } + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + var insert = editDistanceDFS(s, t, i, j - 1); + var delete = editDistanceDFS(s, t, i - 1, j); + var replace = editDistanceDFS(s, t, i - 1, j - 1); + // 最小編集回数を返す + return @min(@min(insert, delete), replace) + 1; +} + +// 編集距離:メモ化探索 +fn editDistanceDFSMem(comptime s: []const u8, comptime t: []const u8, mem: anytype, i: usize, j: usize) i32 { + // s と t がともに空なら 0 を返す + if (i == 0 and j == 0) { + return 0; + } + // s が空なら t の長さを返す + if (i == 0) { + return @intCast(j); + } + // t が空なら s の長さを返す + if (j == 0) { + return @intCast(i); + } + // 記録済みなら、それをそのまま返す + if (mem[i][j] != -1) { + return mem[i][j]; + } + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + if (s[i - 1] == t[j - 1]) { + return editDistanceDFSMem(s, t, mem, i - 1, j - 1); + } + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + var insert = editDistanceDFSMem(s, t, mem, i, j - 1); + var delete = editDistanceDFSMem(s, t, mem, i - 1, j); + var replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1); + // 最小編集回数を記録して返す + mem[i][j] = @min(@min(insert, delete), replace) + 1; + return mem[i][j]; +} + +// 編集距離:動的計画法 +fn editDistanceDP(comptime s: []const u8, comptime t: []const u8) i32 { + comptime var n = s.len; + comptime var m = t.len; + var dp = [_][m + 1]i32{[_]i32{0} ** (m + 1)} ** (n + 1); + // 状態遷移:先頭行と先頭列 + for (1..n + 1) |i| { + dp[i][0] = @intCast(i); + } + for (1..m + 1) |j| { + dp[0][j] = @intCast(j); + } + // 状態遷移: 残りの行と列 + for (1..n + 1) |i| { + for (1..m + 1) |j| { + if (s[i - 1] == t[j - 1]) { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[i][j] = dp[i - 1][j - 1]; + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[i][j] = @min(@min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1; + } + } + } + return dp[n][m]; +} + +// 編集距離:空間最適化した動的計画法 +fn editDistanceDPComp(comptime s: []const u8, comptime t: []const u8) i32 { + comptime var n = s.len; + comptime var m = t.len; + var dp = [_]i32{0} ** (m + 1); + // 状態遷移:先頭行 + for (1..m + 1) |j| { + dp[j] = @intCast(j); + } + // 状態遷移:残りの行 + for (1..n + 1) |i| { + // 状態遷移:先頭列 + var leftup = dp[0]; // dp[i-1, j-1] を一時保存する + dp[0] = @intCast(i); + // 状態遷移:残りの列 + for (1..m + 1) |j| { + var temp = dp[j]; + if (s[i - 1] == t[j - 1]) { + // 2 つの文字が等しければ、その 2 文字をそのままスキップする + dp[j] = leftup; + } else { + // 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1 + dp[j] = @min(@min(dp[j - 1], dp[j]), leftup) + 1; + } + leftup = temp; // 次の反復の dp[i-1, j-1] に更新する + } + } + return dp[m]; +} + +// Driver Code +pub fn main() !void { + const s = "bag"; + const t = "pack"; + comptime var n = s.len; + comptime var m = t.len; + + // 全探索 + var res = editDistanceDFS(s, t, n, m); + std.debug.print("{s} を {s} に変更するには最小で {} 回の編集が必要です\n", .{ s, t, res }); + + // メモ化探索 + var mem = [_][m + 1]i32{[_]i32{-1} ** (m + 1)} ** (n + 1); + res = editDistanceDFSMem(s, t, @constCast(&mem), n, m); + std.debug.print("{s} を {s} に変更するには最小で {} 回の編集が必要です\n", .{ s, t, res }); + + // 動的計画法 + res = editDistanceDP(s, t); + std.debug.print("{s} を {s} に変更するには最小で {} 回の編集が必要です\n", .{ s, t, res }); + + // 空間最適化後の動的計画法 + res = editDistanceDPComp(s, t); + std.debug.print("{s} を {s} に変更するには最小で {} 回の編集が必要です\n", .{ s, t, res }); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_dynamic_programming/knapsack.zig b/ja/codes/zig/chapter_dynamic_programming/knapsack.zig new file mode 100644 index 000000000..6b3c50a2c --- /dev/null +++ b/ja/codes/zig/chapter_dynamic_programming/knapsack.zig @@ -0,0 +1,110 @@ +// File: knapsack.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 0-1 ナップサック:総当たり探索 +fn knapsackDFS(wgt: []i32, val: []i32, i: usize, c: usize) i32 { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if (i == 0 or c == 0) { + return 0; + } + // ナップサック容量を超える場合は、入れない選択しかできない + if (wgt[i - 1] > c) { + return knapsackDFS(wgt, val, i - 1, c); + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + var no = knapsackDFS(wgt, val, i - 1, c); + var yes = knapsackDFS(wgt, val, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; + // 2つの案のうち価値が大きいほうを返す + return @max(no, yes); +} + +// 0-1 ナップサック:メモ化探索 +fn knapsackDFSMem(wgt: []i32, val: []i32, mem: anytype, i: usize, c: usize) i32 { + // すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す + if (i == 0 or c == 0) { + return 0; + } + // 既に記録があればそのまま返す + if (mem[i][c] != -1) { + return mem[i][c]; + } + // ナップサック容量を超える場合は、入れない選択しかできない + if (wgt[i - 1] > c) { + return knapsackDFSMem(wgt, val, mem, i - 1, c); + } + // 品物 i を入れない場合と入れる場合の最大価値を計算する + var no = knapsackDFSMem(wgt, val, mem, i - 1, c); + var yes = knapsackDFSMem(wgt, val, mem, i - 1, c - @as(usize, @intCast(wgt[i - 1]))) + val[i - 1]; + // 2 つの案のうち価値が大きい方を記録して返す + mem[i][c] = @max(no, yes); + return mem[i][c]; +} + +// 0-1 ナップサック:動的計画法 +fn knapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { + comptime var n = wgt.len; + // dp テーブルを初期化 + var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); + // 状態遷移 + for (1..n + 1) |i| { + for (1..cap + 1) |c| { + if (wgt[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[i][c] = dp[i - 1][c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i][c] = @max(dp[i - 1][c], dp[i - 1][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +// 0-1 ナップサック:空間最適化後の動的計画法 +fn knapsackDPComp(wgt: []i32, val: []i32, comptime cap: usize) i32 { + var n = wgt.len; + // dp テーブルを初期化 + var dp = [_]i32{0} ** (cap + 1); + // 状態遷移 + for (1..n + 1) |i| { + // 逆順に走査する + var c = cap; + while (c > 0) : (c -= 1) { + if (wgt[i - 1] < c) { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[cap]; +} + +// Driver Code +pub fn main() !void { + comptime var wgt = [_]i32{ 10, 20, 30, 40, 50 }; + comptime var val = [_]i32{ 50, 120, 150, 210, 240 }; + comptime var cap = 50; + comptime var n = wgt.len; + + // 全探索 + var res = knapsackDFS(&wgt, &val, n, cap); + std.debug.print("ナップサック容量を超えない最大価値は {}\n", .{res}); + + // メモ化探索 + var mem = [_][cap + 1]i32{[_]i32{-1} ** (cap + 1)} ** (n + 1); + res = knapsackDFSMem(&wgt, &val, @constCast(&mem), n, cap); + std.debug.print("ナップサック容量を超えない最大価値は {}\n", .{res}); + + // 動的計画法 + res = knapsackDP(&wgt, &val, cap); + std.debug.print("ナップサック容量を超えない最大価値は {}\n", .{res}); + + // 空間最適化後の動的計画法 + res = knapsackDPComp(&wgt, &val, cap); + std.debug.print("ナップサック容量を超えない最大価値は {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig b/ja/codes/zig/chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig new file mode 100644 index 000000000..f32bbd632 --- /dev/null +++ b/ja/codes/zig/chapter_dynamic_programming/min_cost_climbing_stairs_dp.zig @@ -0,0 +1,54 @@ +// File: min_cost_climbing_stairs_dp.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 階段登りの最小コスト:動的計画法 +fn minCostClimbingStairsDP(comptime cost: []i32) i32 { + comptime var n = cost.len - 1; + if (n == 1 or n == 2) { + return cost[n]; + } + // 部分問題の解を保存するために dp テーブルを初期化 + var dp = [_]i32{-1} ** (n + 1); + // 初期状態:最小部分問題の解をあらかじめ設定 + dp[1] = cost[1]; + dp[2] = cost[2]; + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for (3..n + 1) |i| { + dp[i] = @min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return dp[n]; +} + +// 階段昇りの最小コスト:空間最適化後の動的計画法 +fn minCostClimbingStairsDPComp(cost: []i32) i32 { + var n = cost.len - 1; + if (n == 1 or n == 2) { + return cost[n]; + } + var a = cost[1]; + var b = cost[2]; + // 状態遷移:小さい部分問題から大きい部分問題へ順に解く + for (3..n + 1) |i| { + var tmp = b; + b = @min(a, tmp) + cost[i]; + a = tmp; + } + return b; +} + +// Driver Code +pub fn main() !void { + comptime var cost = [_]i32{ 0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1 }; + std.debug.print("入力された階段のコストのリストは {any}\n", .{cost}); + + var res = minCostClimbingStairsDP(&cost); + std.debug.print("入力された階段のコストのリストは {}\n", .{res}); + + res = minCostClimbingStairsDPComp(&cost); + std.debug.print("入力された階段のコストのリストは {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_dynamic_programming/min_path_sum.zig b/ja/codes/zig/chapter_dynamic_programming/min_path_sum.zig new file mode 100644 index 000000000..307b14387 --- /dev/null +++ b/ja/codes/zig/chapter_dynamic_programming/min_path_sum.zig @@ -0,0 +1,122 @@ +// File: min_path_sum.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 最小経路和:全探索 +fn minPathSumDFS(grid: anytype, i: i32, j: i32) i32 { + // 左上のセルなら探索を終了する + if (i == 0 and j == 0) { + return grid[0][0]; + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if (i < 0 or j < 0) { + return std.math.maxInt(i32); + } + // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する + var up = minPathSumDFS(grid, i - 1, j); + var left = minPathSumDFS(grid, i, j - 1); + // 左上隅から (i, j) までの最小経路コストを返す + return @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; +} + +// 最小経路和:メモ化探索 +fn minPathSumDFSMem(grid: anytype, mem: anytype, i: i32, j: i32) i32 { + // 左上のセルなら探索を終了する + if (i == 0 and j == 0) { + return grid[0][0]; + } + // 行または列のインデックスが範囲外なら、コスト +∞ を返す + if (i < 0 or j < 0) { + return std.math.maxInt(i32); + } + // 既に記録があればそのまま返す + if (mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] != -1) { + return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; + } + // 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する + var up = minPathSumDFSMem(grid, mem, i - 1, j); + var left = minPathSumDFSMem(grid, mem, i, j - 1); + // 左上隅から (i, j) までの最小経路コストを返す + // 左上隅から (i, j) までの最小経路コストを記録して返す + mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))] = @min(left, up) + grid[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; + return mem[@as(usize, @intCast(i))][@as(usize, @intCast(j))]; +} + +// 最小経路和:動的計画法 +fn minPathSumDP(comptime grid: anytype) i32 { + comptime var n = grid.len; + comptime var m = grid[0].len; + // dp テーブルを初期化 + var dp = [_][m]i32{[_]i32{0} ** m} ** n; + dp[0][0] = grid[0][0]; + // 状態遷移:先頭行 + for (1..m) |j| { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + // 状態遷移:先頭列 + for (1..n) |i| { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + // 状態遷移: 残りの行と列 + for (1..n) |i| { + for (1..m) |j| { + dp[i][j] = @min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]; + } + } + return dp[n - 1][m - 1]; +} + +// 最小経路和:空間最適化後の動的計画法 +fn minPathSumDPComp(comptime grid: anytype) i32 { + comptime var n = grid.len; + comptime var m = grid[0].len; + // dp テーブルを初期化 + var dp = [_]i32{0} ** m; + // 状態遷移:先頭行 + dp[0] = grid[0][0]; + for (1..m) |j| { + dp[j] = dp[j - 1] + grid[0][j]; + } + // 状態遷移:残りの行 + for (1..n) |i| { + // 状態遷移:先頭列 + dp[0] = dp[0] + grid[i][0]; + for (1..m) |j| { + dp[j] = @min(dp[j - 1], dp[j]) + grid[i][j]; + } + } + return dp[m - 1]; +} + +// Driver Code +pub fn main() !void { + comptime var grid = [_][4]i32{ + [_]i32{ 1, 3, 1, 5 }, + [_]i32{ 2, 2, 4, 2 }, + [_]i32{ 5, 3, 2, 1 }, + [_]i32{ 4, 3, 5, 2 }, + }; + comptime var n = grid.len; + comptime var m = grid[0].len; + + // 全探索 + var res = minPathSumDFS(&grid, n - 1, m - 1); + std.debug.print("左上から右下までの最小経路和は {}\n", .{res}); + + // メモ化探索 + var mem = [_][m]i32{[_]i32{-1} ** m} ** n; + res = minPathSumDFSMem(&grid, &mem, n - 1, m - 1); + std.debug.print("左上から右下までの最小経路和は {}\n", .{res}); + + // 動的計画法 + res = minPathSumDP(&grid); + std.debug.print("左上から右下までの最小経路和は {}\n", .{res}); + + // 空間最適化後の動的計画法 + res = minPathSumDPComp(&grid); + std.debug.print("左上から右下までの最小経路和は {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_dynamic_programming/unbounded_knapsack.zig b/ja/codes/zig/chapter_dynamic_programming/unbounded_knapsack.zig new file mode 100644 index 000000000..3efd08570 --- /dev/null +++ b/ja/codes/zig/chapter_dynamic_programming/unbounded_knapsack.zig @@ -0,0 +1,62 @@ +// File: unbounded_knapsack.zig +// Created Time: 2023-07-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); + +// 完全ナップサック問題:動的計画法 +fn unboundedKnapsackDP(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { + comptime var n = wgt.len; + // dp テーブルを初期化 + var dp = [_][cap + 1]i32{[_]i32{0} ** (cap + 1)} ** (n + 1); + // 状態遷移 + for (1..n + 1) |i| { + for (1..cap + 1) |c| { + if (wgt[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[i][c] = dp[i - 1][c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[i][c] = @max(dp[i - 1][c], dp[i][c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[n][cap]; +} + +// 完全ナップサック問題:空間最適化後の動的計画法 +fn unboundedKnapsackDPComp(comptime wgt: []i32, val: []i32, comptime cap: usize) i32 { + comptime var n = wgt.len; + // dp テーブルを初期化 + var dp = [_]i32{0} ** (cap + 1); + // 状態遷移 + for (1..n + 1) |i| { + for (1..cap + 1) |c| { + if (wgt[i - 1] > c) { + // ナップサック容量を超えるなら品物 i は選ばない + dp[c] = dp[c]; + } else { + // 品物 i を選ばない場合と選ぶ場合の大きい方 + dp[c] = @max(dp[c], dp[c - @as(usize, @intCast(wgt[i - 1]))] + val[i - 1]); + } + } + } + return dp[cap]; +} + +// Driver Code +pub fn main() !void { + comptime var wgt = [_]i32{ 1, 2, 3 }; + comptime var val = [_]i32{ 5, 11, 15 }; + comptime var cap = 4; + + // 動的計画法 + var res = unboundedKnapsackDP(&wgt, &val, cap); + std.debug.print("ナップサック容量を超えない最大価値は {}\n", .{res}); + + // 空間最適化後の動的計画法 + res = unboundedKnapsackDPComp(&wgt, &val, cap); + std.debug.print("ナップサック容量を超えない最大価値は {}\n", .{res}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_hashing/array_hash_map.zig b/ja/codes/zig/chapter_hashing/array_hash_map.zig new file mode 100644 index 000000000..9b54f2777 --- /dev/null +++ b/ja/codes/zig/chapter_hashing/array_hash_map.zig @@ -0,0 +1,162 @@ +// File: array_hash_map.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// キーと値の組 +const Pair = struct { + key: usize = undefined, + val: []const u8 = undefined, + + pub fn init(key: usize, val: []const u8) Pair { + return Pair { + .key = key, + .val = val, + }; + } +}; + +// 配列ベースのハッシュテーブル +pub fn ArrayHashMap(comptime T: type) type { + return struct { + bucket: ?std.ArrayList(?T) = null, + mem_allocator: std.mem.Allocator = undefined, + + const Self = @This(); + + // コンストラクタ + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + self.mem_allocator = allocator; + // 長さ 100 のバケット(配列)を初期化する + self.bucket = std.ArrayList(?T).init(self.mem_allocator); + var i: i32 = 0; + while (i < 100) : (i += 1) { + try self.bucket.?.append(null); + } + } + + // デストラクタ + pub fn deinit(self: *Self) void { + if (self.bucket != null) self.bucket.?.deinit(); + } + + // ハッシュ関数 + fn hashFunc(key: usize) usize { + var index = key % 100; + return index; + } + + // 検索操作 + pub fn get(self: *Self, key: usize) []const u8 { + var index = hashFunc(key); + var pair = self.bucket.?.items[index]; + return pair.?.val; + } + + // 追加操作 + pub fn put(self: *Self, key: usize, val: []const u8) !void { + var pair = Pair.init(key, val); + var index = hashFunc(key); + self.bucket.?.items[index] = pair; + } + + // 削除操作 + pub fn remove(self: *Self, key: usize) !void { + var index = hashFunc(key); + // null に設定し、削除を表す + self.bucket.?.items[index] = null; + } + + // すべてのキーと値のペアを取得 + pub fn pairSet(self: *Self) !std.ArrayList(T) { + var entry_set = std.ArrayList(T).init(self.mem_allocator); + for (self.bucket.?.items) |item| { + if (item == null) continue; + try entry_set.append(item.?); + } + return entry_set; + } + + // すべてのキーを取得 + pub fn keySet(self: *Self) !std.ArrayList(usize) { + var key_set = std.ArrayList(usize).init(self.mem_allocator); + for (self.bucket.?.items) |item| { + if (item == null) continue; + try key_set.append(item.?.key); + } + return key_set; + } + + // すべての値を取得 + pub fn valueSet(self: *Self) !std.ArrayList([]const u8) { + var value_set = std.ArrayList([]const u8).init(self.mem_allocator); + for (self.bucket.?.items) |item| { + if (item == null) continue; + try value_set.append(item.?.val); + } + return value_set; + } + + // ハッシュテーブルを出力 + pub fn print(self: *Self) !void { + var entry_set = try self.pairSet(); + defer entry_set.deinit(); + for (entry_set.items) |item| { + std.debug.print("{} -> {s}\n", .{item.key, item.val}); + } + } + }; +} + +// Driver Code +pub fn main() !void { + // ハッシュテーブルを初期化 + var map = ArrayHashMap(Pair){}; + try map.init(std.heap.page_allocator); + defer map.deinit(); + + // 追加操作 + // ハッシュテーブルにキーと値の組 (key, value) を追加する + try map.put(12836, "シャオハー"); + try map.put(15937, "シャオルオ"); + try map.put(16750, "シャオスワン"); + try map.put(13276, "シャオファー"); + try map.put(10583, "シャオヤー"); + std.debug.print("\n追加完了後、ハッシュテーブルは\nKey -> Value\n", .{}); + try map.print(); + + // 検索操作 + // ハッシュテーブルにキー key を入力し、値 value を取得する + var name = map.get(15937); + std.debug.print("\n学籍番号 15937 を入力すると、名前 {s} が見つかります\n", .{name}); + + // 削除操作 + // ハッシュテーブルからキーと値の組 (key, value) を削除する + try map.remove(10583); + std.debug.print("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value\n", .{}); + try map.print(); + + // ハッシュテーブルを走査 + std.debug.print("\nキーと値の組 Key->Value を順に表示\n", .{}); + var entry_set = try map.pairSet(); + for (entry_set.items) |kv| { + std.debug.print("{} -> {s}\n", .{kv.key, kv.val}); + } + defer entry_set.deinit(); + std.debug.print("\nキー Key のみを順に表示\n", .{}); + var key_set = try map.keySet(); + for (key_set.items) |key| { + std.debug.print("{}\n", .{key}); + } + defer key_set.deinit(); + std.debug.print("\n値 value のみを順に表示\n", .{}); + var value_set = try map.valueSet(); + for (value_set.items) |val| { + std.debug.print("{s}\n", .{val}); + } + defer value_set.deinit(); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/ja/codes/zig/chapter_hashing/hash_map.zig b/ja/codes/zig/chapter_hashing/hash_map.zig new file mode 100644 index 000000000..de654d269 --- /dev/null +++ b/ja/codes/zig/chapter_hashing/hash_map.zig @@ -0,0 +1,54 @@ +// File: hash_map.zig +// Created Time: 2023-01-13 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // ハッシュテーブルを初期化 + var map = std.AutoHashMap(i32, []const u8).init(std.heap.page_allocator); + // メモリの遅延解放 + defer map.deinit(); + + // 追加操作 + // ハッシュテーブルにキーと値の組 (key, value) を追加する + try map.put(12836, "シャオハー"); + try map.put(15937, "シャオルオ"); + try map.put(16750, "シャオスワン"); + try map.put(13276, "シャオファー"); + try map.put(10583, "シャオヤー"); + std.debug.print("\n追加完了後、ハッシュテーブルは\nKey -> Value\n", .{}); + inc.PrintUtil.printHashMap(i32, []const u8, map); + + // 検索操作 + // ハッシュテーブルにキー key を入力し、値 value を取得する + var name = map.get(15937).?; + std.debug.print("\n学籍番号 15937 を入力すると、名前 {s} が見つかります\n", .{name}); + + // 削除操作 + // ハッシュテーブルからキーと値の組 (key, value) を削除する + _ = map.remove(10583); + std.debug.print("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value\n", .{}); + inc.PrintUtil.printHashMap(i32, []const u8, map); + + // ハッシュテーブルを走査 + std.debug.print("\nキーと値の組 Key->Value を順に表示\n", .{}); + inc.PrintUtil.printHashMap(i32, []const u8, map); + + std.debug.print("\nキー Key のみを順に表示\n", .{}); + var it = map.iterator(); + while (it.next()) |kv| { + std.debug.print("{}\n", .{kv.key_ptr.*}); + } + + std.debug.print("\n値 value のみを順に表示\n", .{}); + it = map.iterator(); + while (it.next()) |kv| { + std.debug.print("{s}\n", .{kv.value_ptr.*}); + } + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/ja/codes/zig/chapter_heap/heap.zig b/ja/codes/zig/chapter_heap/heap.zig new file mode 100644 index 000000000..1ffb2db0d --- /dev/null +++ b/ja/codes/zig/chapter_heap/heap.zig @@ -0,0 +1,80 @@ +// File: heap.zig +// Created Time: 2023-01-14 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +fn lessThan(context: void, a: i32, b: i32) std.math.Order { + _ = context; + return std.math.order(a, b); +} + +fn greaterThan(context: void, a: i32, b: i32) std.math.Order { + return lessThan(context, a, b).invert(); +} + +fn testPush(comptime T: type, mem_allocator: std.mem.Allocator, heap: anytype, val: T) !void { + try heap.add(val); // 要素をヒープに追加 + std.debug.print("\n要素 {} をヒープに追加した後\n", .{val}); + try inc.PrintUtil.printHeap(T, mem_allocator, heap); +} + +fn testPop(comptime T: type, mem_allocator: std.mem.Allocator, heap: anytype) !void { + var val = heap.remove(); // ヒープ頂点の要素を取り出す + std.debug.print("\nヒープの先頭要素 {} を取り出した後\n", .{val}); + try inc.PrintUtil.printHeap(T, mem_allocator, heap); +} + +// Driver Code +pub fn main() !void { + // メモリアロケータを初期化する + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // ヒープを初期化する + // 最小ヒープを初期化する + const PQlt = std.PriorityQueue(i32, void, lessThan); + var min_heap = PQlt.init(std.heap.page_allocator, {}); + defer min_heap.deinit(); + // 最大ヒープを初期化 + const PQgt = std.PriorityQueue(i32, void, greaterThan); + var max_heap = PQgt.init(std.heap.page_allocator, {}); + defer max_heap.deinit(); + + std.debug.print("\n以下のテストケースは最大ヒープ", .{}); + + // 要素をヒープに追加 + try testPush(i32, mem_allocator, &max_heap, 1); + try testPush(i32, mem_allocator, &max_heap, 3); + try testPush(i32, mem_allocator, &max_heap, 2); + try testPush(i32, mem_allocator, &max_heap, 5); + try testPush(i32, mem_allocator, &max_heap, 4); + + // ヒープ頂点の要素を取得 + var peek = max_heap.peek().?; + std.debug.print("\nヒープの先頭要素は {}\n", .{peek}); + + // ヒープ頂点の要素を取り出す + try testPop(i32, mem_allocator, &max_heap); + try testPop(i32, mem_allocator, &max_heap); + try testPop(i32, mem_allocator, &max_heap); + try testPop(i32, mem_allocator, &max_heap); + try testPop(i32, mem_allocator, &max_heap); + + // ヒープのサイズを取得 + var size = max_heap.len; + std.debug.print("\nヒープ要素数は {}\n", .{size}); + + // ヒープが空かどうかを判定 + var is_empty = if (max_heap.len == 0) true else false; + std.debug.print("\nヒープが空かどうか {}\n", .{is_empty}); + + // リストを入力してヒープを構築 + try min_heap.addSlice(&[_]i32{ 1, 3, 2, 5, 4 }); + std.debug.print("\nリストを入力して最小ヒープを構築した後\n", .{}); + try inc.PrintUtil.printHeap(i32, mem_allocator, min_heap); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/ja/codes/zig/chapter_heap/my_heap.zig b/ja/codes/zig/chapter_heap/my_heap.zig new file mode 100644 index 000000000..2110394c5 --- /dev/null +++ b/ja/codes/zig/chapter_heap/my_heap.zig @@ -0,0 +1,186 @@ +// File: my_heap.zig +// Created Time: 2023-01-14 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// ヒープクラスの簡易実装 +pub fn MaxHeap(comptime T: type) type { + return struct { + const Self = @This(); + + max_heap: ?std.ArrayList(T) = null, // 配列ではなくリストを使うことで、拡張を考慮する必要がない + + // コンストラクタ。入力リストに基づいてヒープを構築する + pub fn init(self: *Self, allocator: std.mem.Allocator, nums: []const T) !void { + if (self.max_heap != null) return; + self.max_heap = std.ArrayList(T).init(allocator); + // リスト要素をそのままヒープに追加 + try self.max_heap.?.appendSlice(nums); + // 葉ノード以外のすべてのノードをヒープ化 + var i: usize = parent(self.size() - 1) + 1; + while (i > 0) : (i -= 1) { + try self.siftDown(i - 1); + } + } + + // デストラクタ。メモリを解放 + pub fn deinit(self: *Self) void { + if (self.max_heap != null) self.max_heap.?.deinit(); + } + + // 左子ノードのインデックスを取得 + fn left(i: usize) usize { + return 2 * i + 1; + } + + // 右子ノードのインデックスを取得 + fn right(i: usize) usize { + return 2 * i + 2; + } + + // 親ノードのインデックスを取得 + fn parent(i: usize) usize { + // return (i - 1) / 2; // 切り捨て除算 + return @divFloor(i - 1, 2); + } + + // 要素を交換 + fn swap(self: *Self, i: usize, j: usize) !void { + var tmp = self.max_heap.?.items[i]; + try self.max_heap.?.replaceRange(i, 1, &[_]T{self.max_heap.?.items[j]}); + try self.max_heap.?.replaceRange(j, 1, &[_]T{tmp}); + } + + // ヒープのサイズを取得 + pub fn size(self: *Self) usize { + return self.max_heap.?.items.len; + } + + // ヒープが空かどうかを判定 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // ヒープ先頭要素にアクセス + pub fn peek(self: *Self) T { + return self.max_heap.?.items[0]; + } + + // 要素をヒープに追加 + pub fn push(self: *Self, val: T) !void { + // ノードを追加 + try self.max_heap.?.append(val); + // 下から上へヒープ化 + try self.siftUp(self.size() - 1); + } + + // ノード i から始めて、下から上へヒープ化 + fn siftUp(self: *Self, i_: usize) !void { + var i = i_; + while (true) { + // ノード i の親ノードを取得 + var p = parent(i); + // 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了 + if (p < 0 or self.max_heap.?.items[i] <= self.max_heap.?.items[p]) break; + // 2 つのノードを交換 + try self.swap(i, p); + // ループで下から上へヒープ化 + i = p; + } + } + + // 要素をヒープから取り出す + pub fn pop(self: *Self) !T { + // 判定処理 + if (self.isEmpty()) unreachable; + // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換) + try self.swap(0, self.size() - 1); + // ノードを削除 + var val = self.max_heap.?.pop(); + // 上から下へヒープ化 + try self.siftDown(0); + // ヒープ先頭要素を返す + return val; + } + + // ノード i から始めて、上から下へヒープ化 + fn siftDown(self: *Self, i_: usize) !void { + var i = i_; + while (true) { + // ノード i, l, r のうち値が最大のノードを ma とする + var l = left(i); + var r = right(i); + var ma = i; + if (l < self.size() and self.max_heap.?.items[l] > self.max_heap.?.items[ma]) ma = l; + if (r < self.size() and self.max_heap.?.items[r] > self.max_heap.?.items[ma]) ma = r; + // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける + if (ma == i) break; + // 2 つのノードを交換 + try self.swap(i, ma); + // ループで上から下へヒープ化 + i = ma; + } + } + + fn lessThan(context: void, a: T, b: T) std.math.Order { + _ = context; + return std.math.order(a, b); + } + + fn greaterThan(context: void, a: T, b: T) std.math.Order { + return lessThan(context, a, b).invert(); + } + + // ヒープ(二分木)を出力 + pub fn print(self: *Self, mem_allocator: std.mem.Allocator) !void { + const PQgt = std.PriorityQueue(T, void, greaterThan); + var queue = PQgt.init(std.heap.page_allocator, {}); + defer queue.deinit(); + try queue.addSlice(self.max_heap.?.items); + try inc.PrintUtil.printHeap(T, mem_allocator, queue); + } + }; +} + +// Driver Code +pub fn main() !void { + // メモリアロケータを初期化する + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // 最大ヒープを初期化 + var max_heap = MaxHeap(i32){}; + try max_heap.init(std.heap.page_allocator, &[_]i32{ 9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2 }); + defer max_heap.deinit(); + std.debug.print("\nリストを入力してヒープを構築した後\n", .{}); + try max_heap.print(mem_allocator); + + // ヒープ頂点の要素を取得 + var peek = max_heap.peek(); + std.debug.print("\nヒープの先頭要素は {}\n", .{peek}); + + // 要素をヒープに追加 + const val = 7; + try max_heap.push(val); + std.debug.print("\n要素 {} をヒープに追加した後\n", .{val}); + try max_heap.print(mem_allocator); + + // ヒープ頂点の要素を取り出す + peek = try max_heap.pop(); + std.debug.print("\nヒープの先頭要素 {} を取り出した後\n", .{peek}); + try max_heap.print(mem_allocator); + + // ヒープのサイズを取得 + var size = max_heap.size(); + std.debug.print("\nヒープ要素数は {}", .{size}); + + // ヒープが空かどうかを判定 + var is_empty = max_heap.isEmpty(); + std.debug.print("\nヒープが空かどうか {}\n", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/ja/codes/zig/chapter_searching/binary_search.zig b/ja/codes/zig/chapter_searching/binary_search.zig new file mode 100644 index 000000000..b4cc4ad42 --- /dev/null +++ b/ja/codes/zig/chapter_searching/binary_search.zig @@ -0,0 +1,64 @@ +// File: binary_search.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 二分探索(両閉区間) +fn binarySearch(comptime T: type, nums: std.ArrayList(T), target: T) T { + // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す + var i: usize = 0; + var j: usize = nums.items.len - 1; + // ループし、探索区間が空になったら終了する(i > j で空) + while (i <= j) { + var m = i + (j - i) / 2; // 中点インデックス m を計算 + if (nums.items[m] < target) { // この場合、target は区間 [m+1, j] にある + i = m + 1; + } else if (nums.items[m] > target) { // この場合、target は区間 [i, m-1] にある + j = m - 1; + } else { // 目標要素が見つかったらそのインデックスを返す + return @intCast(m); + } + } + // 目標要素が見つからなければ -1 を返す + return -1; +} + +// 二分探索(左閉右開区間) +fn binarySearchLCRO(comptime T: type, nums: std.ArrayList(T), target: T) T { + // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す + var i: usize = 0; + var j: usize = nums.items.len; + // ループし、探索区間が空になったら終了する(i = j で空) + while (i <= j) { + var m = i + (j - i) / 2; // 中点インデックス m を計算 + if (nums.items[m] < target) { // この場合、target は区間 [m+1, j) にある + i = m + 1; + } else if (nums.items[m] > target) { // この場合、target は区間 [i, m) にある + j = m; + } else { // 目標要素が見つかったらそのインデックスを返す + return @intCast(m); + } + } + // 目標要素が見つからなければ -1 を返す + return -1; +} + +// Driver Code +pub fn main() !void { + var target: i32 = 6; + var nums = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums.deinit(); + try nums.appendSlice(&[_]i32{ 1, 3, 6, 8, 12, 15, 23, 26, 31, 35 }); + + // 二分探索(両閉区間) + var index = binarySearch(i32, nums, target); + std.debug.print("対象要素 6 のインデックス = {}\n", .{index}); + + // 二分探索(左閉右開区間) + index = binarySearchLCRO(i32, nums, target); + std.debug.print("対象要素 6 のインデックス = {}\n", .{index}); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/ja/codes/zig/chapter_searching/hashing_search.zig b/ja/codes/zig/chapter_searching/hashing_search.zig new file mode 100644 index 000000000..a6ba2d934 --- /dev/null +++ b/ja/codes/zig/chapter_searching/hashing_search.zig @@ -0,0 +1,57 @@ +// File: hashing_search.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// ハッシュ探索(配列) +fn hashingSearchArray(comptime T: type, map: std.AutoHashMap(T, T), target: T) T { + // ハッシュテーブルの key: 目標要素、value: インデックス + // ハッシュテーブルにこの key がなければ -1 を返す + if (map.getKey(target) == null) return -1; + return map.get(target).?; +} + +// ハッシュ探索(連結リスト) +fn hashingSearchLinkedList(comptime T: type, map: std.AutoHashMap(T, *inc.ListNode(T)), target: T) ?*inc.ListNode(T) { + // ハッシュテーブルの key: 目標ノード値、value: ノードオブジェクト + // ハッシュテーブルにこの key がなければ null を返す + if (map.getKey(target) == null) return null; + return map.get(target); +} + +// Driver Code +pub fn main() !void { + var target: i32 = 3; + + // ハッシュ探索(配列) + var nums = [_]i32{ 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; + // ハッシュテーブルを初期化 + var map = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); + defer map.deinit(); + for (nums, 0..) |num, i| { + try map.put(num, @as(i32, @intCast(i))); // key: 要素、value: インデックス + } + var index = hashingSearchArray(i32, map, target); + std.debug.print("対象要素 3 のインデックス = {}\n", .{index}); + + // ハッシュ探索(連結リスト) + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var head = try inc.ListUtil.arrToLinkedList(i32, mem_allocator, &nums); + // ハッシュテーブルを初期化 + var map1 = std.AutoHashMap(i32, *inc.ListNode(i32)).init(std.heap.page_allocator); + defer map1.deinit(); + while (head != null) { + try map1.put(head.?.val, head.?); + head = head.?.next; + } + var node = hashingSearchLinkedList(i32, map1, target); + std.debug.print("対象ノード値 3 に対応するノードオブジェクトは ", .{}); + try inc.PrintUtil.printLinkedList(i32, node); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/ja/codes/zig/chapter_searching/linear_search.zig b/ja/codes/zig/chapter_searching/linear_search.zig new file mode 100644 index 000000000..fabb2bdd8 --- /dev/null +++ b/ja/codes/zig/chapter_searching/linear_search.zig @@ -0,0 +1,54 @@ +// File: linear_search.zig +// Created Time: 2023-01-13 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 線形探索(配列) +fn linearSearchArray(comptime T: type, nums: std.ArrayList(T), target: T) T { + // 配列を走査 + for (nums.items, 0..) |num, i| { + // 対象要素が見つかったら、その添字を返す + if (num == target) { + return @intCast(i); + } + } + // 目標要素が見つからなければ -1 を返す + return -1; +} + +// 線形探索(連結リスト) +pub fn linearSearchLinkedList(comptime T: type, node: ?*inc.ListNode(T), target: T) ?*inc.ListNode(T) { + var head = node; + // 連結リストを走査 + while (head != null) { + // 対象ノードが見つかったら、それを返す + if (head.?.val == target) return head; + head = head.?.next; + } + return null; +} + +// Driver Code +pub fn main() !void { + var target: i32 = 3; + + // 配列で線形探索を行う + var nums = std.ArrayList(i32).init(std.heap.page_allocator); + defer nums.deinit(); + try nums.appendSlice(&[_]i32{ 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }); + var index = linearSearchArray(i32, nums, target); + std.debug.print("対象要素 3 のインデックス = {}\n", .{index}); + + // 連結リストで線形探索を行う + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var head = try inc.ListUtil.listToLinkedList(i32, mem_allocator, nums); + var node = linearSearchLinkedList(i32, head, target); + std.debug.print("対象ノード値 3 に対応するノードオブジェクトは ", .{}); + try inc.PrintUtil.printLinkedList(i32, node); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/ja/codes/zig/chapter_searching/two_sum.zig b/ja/codes/zig/chapter_searching/two_sum.zig new file mode 100644 index 000000000..6c2c46c43 --- /dev/null +++ b/ja/codes/zig/chapter_searching/two_sum.zig @@ -0,0 +1,58 @@ +// File: two_sum.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 方法 1:総当たり列挙 +pub fn twoSumBruteForce(nums: []i32, target: i32) ?[2]i32 { + var size: usize = nums.len; + var i: usize = 0; + // 2重ループのため、時間計算量は O(n^2) + while (i < size - 1) : (i += 1) { + var j = i + 1; + while (j < size) : (j += 1) { + if (nums[i] + nums[j] == target) { + return [_]i32{@intCast(i), @intCast(j)}; + } + } + } + return null; +} + +// 方法 2:補助ハッシュテーブル +pub fn twoSumHashTable(nums: []i32, target: i32) !?[2]i32 { + var size: usize = nums.len; + // 補助ハッシュテーブルを使用し、空間計算量は O(n) + var dic = std.AutoHashMap(i32, i32).init(std.heap.page_allocator); + defer dic.deinit(); + var i: usize = 0; + // 単一ループで、時間計算量は O(n) + while (i < size) : (i += 1) { + if (dic.contains(target - nums[i])) { + return [_]i32{dic.get(target - nums[i]).?, @intCast(i)}; + } + try dic.put(nums[i], @intCast(i)); + } + return null; +} + + +pub fn main() !void { + // ======= Test Case ======= + var nums = [_]i32{ 2, 7, 11, 15 }; + var target: i32 = 9; + + // ====== Driver Code ====== + // 方法 1 + var res = twoSumBruteForce(&nums, target).?; + std.debug.print("方法1 res = ", .{}); + inc.PrintUtil.printArray(i32, &res); + // 方法 2 + res = (try twoSumHashTable(&nums, target)).?; + std.debug.print("\n方法2 res = ", .{}); + inc.PrintUtil.printArray(i32, &res); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_sorting/bubble_sort.zig b/ja/codes/zig/chapter_sorting/bubble_sort.zig new file mode 100644 index 000000000..d56045dd9 --- /dev/null +++ b/ja/codes/zig/chapter_sorting/bubble_sort.zig @@ -0,0 +1,61 @@ +// File: bubble_sort.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// バブルソート +fn bubbleSort(nums: []i32) void { + // 外側のループ:未ソート区間は [0, i] + var i: usize = nums.len - 1; + while (i > 0) : (i -= 1) { + var j: usize = 0; + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + while (j < i) : (j += 1) { + if (nums[j] > nums[j + 1]) { + // nums[j] と nums[j + 1] を交換 + var tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } +} + +// バブルソート(フラグ最適化) +fn bubbleSortWithFlag(nums: []i32) void { + // 外側のループ:未ソート区間は [0, i] + var i: usize = nums.len - 1; + while (i > 0) : (i -= 1) { + var flag = false; // フラグを初期化する + var j: usize = 0; + // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換 + while (j < i) : (j += 1) { + if (nums[j] > nums[j + 1]) { + // nums[j] と nums[j + 1] を交換 + var tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; + } + } + if (!flag) break; // このバブル処理で要素交換が一度もなければそのまま終了 + } +} + +// Driver Code +pub fn main() !void { + var nums = [_]i32{ 4, 1, 3, 1, 5, 2 }; + bubbleSort(&nums); + std.debug.print("バブルソート完了後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + var nums1 = [_]i32{ 4, 1, 3, 1, 5, 2 }; + bubbleSortWithFlag(&nums1); + std.debug.print("\nバブルソート完了後 nums1 = ", .{}); + inc.PrintUtil.printArray(i32, &nums1); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/ja/codes/zig/chapter_sorting/insertion_sort.zig b/ja/codes/zig/chapter_sorting/insertion_sort.zig new file mode 100644 index 000000000..5afda3067 --- /dev/null +++ b/ja/codes/zig/chapter_sorting/insertion_sort.zig @@ -0,0 +1,31 @@ +// File: insertion_sort.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 挿入ソート +fn insertionSort(nums: []i32) void { + // 外側ループ:整列済み区間は [0, i-1] + var i: usize = 1; + while (i < nums.len) : (i += 1) { + var base = nums[i]; + var j: usize = i; + // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する + while (j >= 1 and nums[j - 1] > base) : (j -= 1) { + nums[j] = nums[j - 1]; // nums[j] を 1 つ右へ移動する + } + nums[j] = base; // base を正しい位置に配置する + } +} + +// Driver Code +pub fn main() !void { + var nums = [_]i32{ 4, 1, 3, 1, 5, 2 }; + insertionSort(&nums); + std.debug.print("挿入ソート完了後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/ja/codes/zig/chapter_sorting/merge_sort.zig b/ja/codes/zig/chapter_sorting/merge_sort.zig new file mode 100644 index 000000000..53b4944f3 --- /dev/null +++ b/ja/codes/zig/chapter_sorting/merge_sort.zig @@ -0,0 +1,67 @@ +// File: merge_sort.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 左部分配列と右部分配列をマージする +// 左部分配列の区間は [left, mid] +// 右部分配列の区間は [mid + 1, right] +fn merge(nums: []i32, left: usize, mid: usize, right: usize) !void { + // 補助配列を初期化する + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var tmp = try mem_allocator.alloc(i32, right + 1 - left); + std.mem.copy(i32, tmp, nums[left..right+1]); + // 左部分配列の開始添字と終了添字 + var leftStart = left - left; + var leftEnd = mid - left; + // 右部分配列の開始インデックスと終了インデックス + var rightStart = mid + 1 - left; + var rightEnd = right - left; + // i, j はそれぞれ左部分配列と右部分配列の先頭要素を指す + var i = leftStart; + var j = rightStart; + // 元の配列 nums を上書きして左部分配列と右部分配列をマージする + var k = left; + while (k <= right) : (k += 1) { + // 「左部分配列のマージがすべて完了している」場合は、右部分配列の要素を選び、`j++` する + if (i > leftEnd) { + nums[k] = tmp[j]; + j += 1; + // そうでなければ、「右部分配列のマージがすべて完了している」または「左部分配列の要素 <= 右部分配列の要素」の場合、左部分配列の要素を選び、i++ する + } else if (j > rightEnd or tmp[i] <= tmp[j]) { + nums[k] = tmp[i]; + i += 1; + // そうでなければ、「左右の部分配列のマージがどちらも完了しておらず」かつ「左部分配列の要素 > 右部分配列の要素」の場合、右部分配列の要素を選び、j++ する + } else { + nums[k] = tmp[j]; + j += 1; + } + } +} + +// マージソート +fn mergeSort(nums: []i32, left: usize, right: usize) !void { + // 終了条件 + if (left >= right) return; // 部分配列の長さが 1 になったら再帰を終了 + // 分割フェーズ + var mid = left + (right - left) / 2; // 中点を計算 + try mergeSort(nums, left, mid); // 左部分配列を再帰処理 + try mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理 + // マージフェーズ + try merge(nums, left, mid, right); +} + +// Driver Code +pub fn main() !void { + // マージソート + var nums = [_]i32{ 7, 3, 2, 6, 0, 1, 5, 4 }; + try mergeSort(&nums, 0, nums.len - 1); + std.debug.print("マージソート完了後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_sorting/quick_sort.zig b/ja/codes/zig/chapter_sorting/quick_sort.zig new file mode 100644 index 000000000..a3c7a3f98 --- /dev/null +++ b/ja/codes/zig/chapter_sorting/quick_sort.zig @@ -0,0 +1,162 @@ +// File: quick_sort.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// クイックソートクラス +const QuickSort = struct { + + // 要素の交換 + pub fn swap(nums: []i32, i: usize, j: usize) void { + var tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + // 番兵分割 + pub fn partition(nums: []i32, left: usize, right: usize) usize { + // nums[left] を基準値とする + var i = left; + var j = right; + while (i < j) { + while (i < j and nums[j] >= nums[left]) j -= 1; // 右から左へ基準値未満の最初の要素を探す + while (i < j and nums[i] <= nums[left]) i += 1; // 左から右へ基準値より大きい最初の要素を探す + swap(nums, i, j); // この 2 つの要素を交換 + } + swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する + return i; // 基準値のインデックスを返す + } + + // クイックソート + pub fn quickSort(nums: []i32, left: usize, right: usize) void { + // 部分配列の長さが 1 なら再帰を終了する + if (left >= right) return; + // 番兵分割 + var pivot = partition(nums, left, right); + // 左右の部分配列を再帰処理 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +}; + +// クイックソートクラス(中央値ピボット最適化) +const QuickSortMedian = struct { + + // 要素の交換 + pub fn swap(nums: []i32, i: usize, j: usize) void { + var tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + // 3つの候補要素の中央値を選ぶ + pub fn medianThree(nums: []i32, left: usize, mid: usize, right: usize) usize { + var l = nums[left]; + var m = nums[mid]; + var r = nums[right]; + if ((l <= m && m <= r) || (r <= m && m <= l)) + return mid; // m は l と r の間 + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // l は m と r の間 + return right; + } + + // 番兵による分割処理(3 点中央値) + pub fn partition(nums: []i32, left: usize, right: usize) usize { + // 3つの候補要素の中央値を選ぶ + var med = medianThree(nums, left, (left + right) / 2, right); + // 中央値を配列の最左端に交換する + swap(nums, left, med); + // nums[left] を基準値とする + var i = left; + var j = right; + while (i < j) { + while (i < j and nums[j] >= nums[left]) j -= 1; // 右から左へ基準値未満の最初の要素を探す + while (i < j and nums[i] <= nums[left]) i += 1; // 左から右へ基準値より大きい最初の要素を探す + swap(nums, i, j); // この 2 つの要素を交換 + } + swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する + return i; // 基準値のインデックスを返す + } + + // クイックソート + pub fn quickSort(nums: []i32, left: usize, right: usize) void { + // 部分配列の長さが 1 なら再帰を終了する + if (left >= right) return; + // 番兵分割 + var pivot = partition(nums, left, right); + if (pivot == 0) return; + // 左右の部分配列を再帰処理 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } +}; + +// クイックソートクラス(再帰深度最適化) +const QuickSortTailCall = struct { + + // 要素の交換 + pub fn swap(nums: []i32, i: usize, j: usize) void { + var tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + // 番兵分割 + pub fn partition(nums: []i32, left: usize, right: usize) usize { + // nums[left] を基準値とする + var i = left; + var j = right; + while (i < j) { + while (i < j and nums[j] >= nums[left]) j -= 1; // 右から左へ基準値未満の最初の要素を探す + while (i < j and nums[i] <= nums[left]) i += 1; // 左から右へ基準値より大きい最初の要素を探す + swap(nums, i, j); // この 2 つの要素を交換 + } + swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する + return i; // 基準値のインデックスを返す + } + + // クイックソート(再帰深度最適化) + pub fn quickSort(nums: []i32, left_: usize, right_: usize) void { + var left = left_; + var right = right_; + // 部分配列の長さが 1 なら再帰を終了する + while (left < right) { + // 番兵による分割処理 + var pivot = partition(nums, left, right); + // 2 つの部分配列のうち短いほうにクイックソートを適用する + if (pivot - left < right - pivot) { + quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソート + left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right] + } else { + quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソート + right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1] + } + } + } +}; + +// Driver Code +pub fn main() !void { + // クイックソート + var nums = [_]i32{ 2, 4, 1, 0, 3, 5 }; + QuickSort.quickSort(&nums, 0, nums.len - 1); + std.debug.print("クイックソート完了後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + // クイックソート(中央値の基準値で最適化) + var nums1 = [_]i32{ 2, 4, 1, 0, 3, 5 }; + QuickSortMedian.quickSort(&nums1, 0, nums1.len - 1); + std.debug.print("\nクイックソート(中央値ピボット最適化)完了後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums1); + + // クイックソート(再帰深度最適化) + var nums2 = [_]i32{ 2, 4, 1, 0, 3, 5 }; + QuickSortTailCall.quickSort(&nums2, 0, nums2.len - 1); + std.debug.print("\nクイックソート(再帰深度最適化)完了後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums2); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/ja/codes/zig/chapter_sorting/radix_sort.zig b/ja/codes/zig/chapter_sorting/radix_sort.zig new file mode 100644 index 000000000..53939e012 --- /dev/null +++ b/ja/codes/zig/chapter_sorting/radix_sort.zig @@ -0,0 +1,77 @@ +// File: radix_sort.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 要素 num の下から k 桁目を取得(exp = 10^(k-1)) +fn digit(num: i32, exp: i32) i32 { + // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す + return @mod(@divFloor(num, exp), 10); +} + +// 計数ソート(nums の k 桁目でソート) +fn countingSortDigit(nums: []i32, exp: i32) !void { + // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要 + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + // defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + var counter = try mem_allocator.alloc(usize, 10); + @memset(counter, 0); + var n = nums.len; + // 0~9 の各数字の出現回数を集計する + for (nums) |num| { + var d: u32 = @bitCast(digit(num, exp)); // nums[i] の第 k 位を取得し、d とする + counter[d] += 1; // 数字 d の出現回数を数える + } + // 累積和を求め、「出現回数」を「配列インデックス」に変換する + var i: usize = 1; + while (i < 10) : (i += 1) { + counter[i] += counter[i - 1]; + } + // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する + var res = try mem_allocator.alloc(i32, n); + i = n - 1; + while (i >= 0) : (i -= 1) { + var d: u32 = @bitCast(digit(nums[i], exp)); + var j = counter[d] - 1; // d の配列内インデックス j を取得する + res[j] = nums[i]; // 現在の要素をインデックス j に格納する + counter[d] -= 1; // d の個数を 1 減らす + if (i == 0) break; + } + // 結果で元の配列 nums を上書きする + i = 0; + while (i < n) : (i += 1) { + nums[i] = res[i]; + } +} + +// 基数ソート +fn radixSort(nums: []i32) !void { + // 最大桁数の判定用に配列の最大要素を取得 + var m: i32 = std.math.minInt(i32); + for (nums) |num| { + if (num > m) m = num; + } + // 下位桁から上位桁の順に走査する + var exp: i32 = 1; + while (exp <= m) : (exp *= 10) { + // 配列要素の k 桁目に対して計数ソートを行う + // k = 1 -> exp = 1 + // k = 2 -> exp = 10 + // つまり exp = 10^(k-1) + try countingSortDigit(nums, exp); + } +} + +// Driver Code +pub fn main() !void { + // 基数ソート + var nums = [_]i32{ 23, 12, 3, 4, 788, 192 }; + try radixSort(&nums); + std.debug.print("基数ソート完了後 nums = ", .{}); + inc.PrintUtil.printArray(i32, &nums); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/ja/codes/zig/chapter_stack_and_queue/array_queue.zig b/ja/codes/zig/chapter_stack_and_queue/array_queue.zig new file mode 100644 index 000000000..df882403c --- /dev/null +++ b/ja/codes/zig/chapter_stack_and_queue/array_queue.zig @@ -0,0 +1,140 @@ +// File: array_queue.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 循環配列ベースのキュー +pub fn ArrayQueue(comptime T: type) type { + return struct { + const Self = @This(); + + nums: []T = undefined, // キュー要素を格納する配列 + cap: usize = 0, // キューの容量 + front: usize = 0, // 先頭ポインタ。先頭要素を指す + queSize: usize = 0, // 末尾ポインタ。キューの末尾 + 1 を指す + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // メモリアロケータ + + // コンストラクタ(メモリを確保して配列を初期化) + pub fn init(self: *Self, allocator: std.mem.Allocator, cap: usize) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.cap = cap; + self.nums = try self.mem_allocator.alloc(T, self.cap); + @memset(self.nums, @as(T, 0)); + } + + // デストラクタ(メモリを解放する) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // キューの容量を取得 + pub fn capacity(self: *Self) usize { + return self.cap; + } + + // キューの長さを取得 + pub fn size(self: *Self) usize { + return self.queSize; + } + + // キューが空かどうかを判定 + pub fn isEmpty(self: *Self) bool { + return self.queSize == 0; + } + + // エンキュー + pub fn push(self: *Self, num: T) !void { + if (self.size() == self.capacity()) { + std.debug.print("キューがいっぱいです\n", .{}); + return; + } + // 末尾ポインタを計算し、末尾インデックス + 1 を指す + // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする + var rear = (self.front + self.queSize) % self.capacity(); + // 末尾ノードの後ろに num を追加 + self.nums[rear] = num; + self.queSize += 1; + } + + // デキュー + pub fn pop(self: *Self) T { + var num = self.peek(); + // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す + self.front = (self.front + 1) % self.capacity(); + self.queSize -= 1; + return num; + } + + // キュー先頭の要素にアクセス + pub fn peek(self: *Self) T { + if (self.isEmpty()) @panic("キューが空です"); + return self.nums[self.front]; + } + + // 配列を返す + pub fn toArray(self: *Self) ![]T { + // 有効長の範囲内のリスト要素のみを変換 + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + var j: usize = self.front; + while (i < self.size()) : ({ i += 1; j += 1; }) { + res[i] = self.nums[j % self.capacity()]; + } + return res; + } + }; +} + +// Driver Code +pub fn main() !void { + // キューを初期化 + var capacity: usize = 10; + var queue = ArrayQueue(i32){}; + try queue.init(std.heap.page_allocator, capacity); + defer queue.deinit(); + + // 要素をエンキュー + try queue.push(1); + try queue.push(3); + try queue.push(2); + try queue.push(5); + try queue.push(4); + std.debug.print("キュー queue = ", .{}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + + // キュー先頭の要素にアクセス + var peek = queue.peek(); + std.debug.print("\n先頭要素 peek = {}", .{peek}); + + // 要素をデキュー + var pop = queue.pop(); + std.debug.print("\nデキューした要素 pop = {}、デキュー後 queue = ", .{pop}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + + // キューの長さを取得 + var size = queue.size(); + std.debug.print("\nキューの長さ size = {}", .{size}); + + // キューが空かどうかを判定 + var is_empty = queue.isEmpty(); + std.debug.print("\nキューが空かどうか = {}", .{is_empty}); + + // 循環配列をテストする + var i: i32 = 0; + while (i < 10) : (i += 1) { + try queue.push(i); + _ = queue.pop(); + std.debug.print("\n第 {} 回のエンキュー + デキュー後 queue = ", .{i}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + } + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_stack_and_queue/array_stack.zig b/ja/codes/zig/chapter_stack_and_queue/array_stack.zig new file mode 100644 index 000000000..a174c6b36 --- /dev/null +++ b/ja/codes/zig/chapter_stack_and_queue/array_stack.zig @@ -0,0 +1,97 @@ +// File: array_stack.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 配列ベースのスタック +pub fn ArrayStack(comptime T: type) type { + return struct { + const Self = @This(); + + stack: ?std.ArrayList(T) = null, + + // コンストラクタ(メモリを確保してスタックを初期化) + pub fn init(self: *Self, allocator: std.mem.Allocator) void { + if (self.stack == null) { + self.stack = std.ArrayList(T).init(allocator); + } + } + + // デストラクタ(メモリを解放) + pub fn deinit(self: *Self) void { + if (self.stack == null) return; + self.stack.?.deinit(); + } + + // スタックの長さを取得 + pub fn size(self: *Self) usize { + return self.stack.?.items.len; + } + + // スタックが空かどうかを判定 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // スタックトップの要素にアクセス + pub fn peek(self: *Self) T { + if (self.isEmpty()) @panic("スタックが空です"); + return self.stack.?.items[self.size() - 1]; + } + + // プッシュ + pub fn push(self: *Self, num: T) !void { + try self.stack.?.append(num); + } + + // ポップ + pub fn pop(self: *Self) T { + var num = self.stack.?.pop(); + return num; + } + + // ArrayList を返す + pub fn toList(self: *Self) std.ArrayList(T) { + return self.stack.?; + } + }; +} + +// Driver Code +pub fn main() !void { + // スタックを初期化 + var stack = ArrayStack(i32){}; + stack.init(std.heap.page_allocator); + // メモリの遅延解放 + defer stack.deinit(); + + // 要素をプッシュ + try stack.push(1); + try stack.push(3); + try stack.push(2); + try stack.push(5); + try stack.push(4); + std.debug.print("スタック stack = ", .{}); + inc.PrintUtil.printList(i32, stack.toList()); + + // スタックトップの要素にアクセス + var peek = stack.peek(); + std.debug.print("\nスタックトップ要素 peek = {}", .{peek}); + + // 要素をポップ + var top = stack.pop(); + std.debug.print("\nポップした要素 pop = {}、ポップ後 stack = ", .{top}); + inc.PrintUtil.printList(i32, stack.toList()); + + // スタックの長さを取得 + var size = stack.size(); + std.debug.print("\nスタックの長さ size = {}", .{size}); + + // スタックが空かどうかを判定 + var is_empty = stack.isEmpty(); + std.debug.print("\nスタックが空かどうか = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_stack_and_queue/deque.zig b/ja/codes/zig/chapter_stack_and_queue/deque.zig new file mode 100644 index 000000000..1a382ca19 --- /dev/null +++ b/ja/codes/zig/chapter_stack_and_queue/deque.zig @@ -0,0 +1,51 @@ +// File: deque.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // 両端キューを初期化 + const L = std.TailQueue(i32); + var deque = L{}; + + // 要素をエンキュー + var node1 = L.Node{ .data = 2 }; + var node2 = L.Node{ .data = 5 }; + var node3 = L.Node{ .data = 4 }; + var node4 = L.Node{ .data = 3 }; + var node5 = L.Node{ .data = 1 }; + deque.append(&node1); // 末尾に追加する + deque.append(&node2); + deque.append(&node3); + deque.prepend(&node4); // 先頭に追加する + deque.prepend(&node5); + std.debug.print("両端キュー deque = ", .{}); + inc.PrintUtil.printQueue(i32, deque); + + // 要素にアクセス + var peek_first = deque.first.?.data; // 先頭要素 + std.debug.print("\n先頭要素 peek_first = {}", .{peek_first}); + var peek_last = deque.last.?.data; // 末尾要素 + std.debug.print("\n末尾要素 peek_last = {}", .{peek_last}); + + // 要素をデキュー + var pop_first = deque.popFirst().?.data; // 先頭要素を取り出す + std.debug.print("\n先頭からデキューした要素 pop_first = {}、先頭からデキュー後 deque = ", .{pop_first}); + inc.PrintUtil.printQueue(i32, deque); + var pop_last = deque.pop().?.data; // 末尾要素を取り出す + std.debug.print("\n末尾からデキューした要素 pop_last = {}、末尾からデキュー後 deque = ", .{pop_last}); + inc.PrintUtil.printQueue(i32, deque); + + // 両端キューの長さを取得 + var size = deque.len; + std.debug.print("\n両端キューの長さ size = {}", .{size}); + + // 両端キューが空かどうかを判定 + var is_empty = if (deque.len == 0) true else false; + std.debug.print("\n両端キューが空かどうか = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_stack_and_queue/linkedlist_deque.zig b/ja/codes/zig/chapter_stack_and_queue/linkedlist_deque.zig new file mode 100644 index 000000000..bd9950394 --- /dev/null +++ b/ja/codes/zig/chapter_stack_and_queue/linkedlist_deque.zig @@ -0,0 +1,207 @@ +// File: linkedlist_deque.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 双方向連結リストノード +pub fn ListNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = undefined, // ノード値 + next: ?*Self = null, // 後継ノードへのポインタ + prev: ?*Self = null, // 前駆ノードへのポインタ + + // Initialize a list node with specific value + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.next = null; + self.prev = null; + } + }; +} + +// 双方向連結リストベースの両端キュー +pub fn LinkedListDeque(comptime T: type) type { + return struct { + const Self = @This(); + + front: ?*ListNode(T) = null, // 先頭ノード front + rear: ?*ListNode(T) = null, // 末尾ノード rear + que_size: usize = 0, // 両端キューの長さ + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // メモリアロケータ + + // コンストラクタ(メモリ確保 + キューを初期化) + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.front = null; + self.rear = null; + self.que_size = 0; + } + + // デストラクタ(メモリを解放する) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 両端キューの長さを取得 + pub fn size(self: *Self) usize { + return self.que_size; + } + + // 両端キューが空かどうかを判定 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // エンキュー操作 + pub fn push(self: *Self, num: T, is_front: bool) !void { + var node = try self.mem_allocator.create(ListNode(T)); + node.init(num); + // 連結リストが空なら、front と rear の両方を node に向ける + if (self.isEmpty()) { + self.front = node; + self.rear = node; + // 先頭へのエンキュー操作 + } else if (is_front) { + // node を連結リストの先頭に追加 + self.front.?.prev = node; + node.next = self.front; + self.front = node; // 先頭ノードを更新する + // 末尾へのエンキュー操作 + } else { + // node を連結リストの末尾に追加 + self.rear.?.next = node; + node.prev = self.rear; + self.rear = node; // 末尾ノードを更新する + } + self.que_size += 1; // キューの長さを更新 + } + + // キュー先頭にエンキュー + pub fn pushFirst(self: *Self, num: T) !void { + try self.push(num, true); + } + + // キュー末尾にエンキュー + pub fn pushLast(self: *Self, num: T) !void { + try self.push(num, false); + } + + // デキュー操作 + pub fn pop(self: *Self, is_front: bool) T { + if (self.isEmpty()) @panic("両端キューが空です"); + var val: T = undefined; + // キュー先頭からの取り出し + if (is_front) { + val = self.front.?.val; // 先頭ノードの値を一時保存 + // 先頭ノードを削除 + var fNext = self.front.?.next; + if (fNext != null) { + fNext.?.prev = null; + self.front.?.next = null; + } + self.front = fNext; // 先頭ノードを更新する + // キュー末尾からの取り出し + } else { + val = self.rear.?.val; // 末尾ノードの値を一時保存 + // 末尾ノードを削除 + var rPrev = self.rear.?.prev; + if (rPrev != null) { + rPrev.?.next = null; + self.rear.?.prev = null; + } + self.rear = rPrev; // 末尾ノードを更新する + } + self.que_size -= 1; // キューの長さを更新 + return val; + } + + // キュー先頭からデキュー + pub fn popFirst(self: *Self) T { + return self.pop(true); + } + + // キュー末尾からデキュー + pub fn popLast(self: *Self) T { + return self.pop(false); + } + + // キュー先頭の要素にアクセス + pub fn peekFirst(self: *Self) T { + if (self.isEmpty()) @panic("両端キューが空です"); + return self.front.?.val; + } + + // キュー末尾の要素にアクセス + pub fn peekLast(self: *Self) T { + if (self.isEmpty()) @panic("両端キューが空です"); + return self.rear.?.val; + } + + // 出力用の配列を返す + pub fn toArray(self: *Self) ![]T { + var node = self.front; + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + while (i < res.len) : (i += 1) { + res[i] = node.?.val; + node = node.?.next; + } + return res; + } + }; +} + +// Driver Code +pub fn main() !void { + // 両端キューを初期化 + var deque = LinkedListDeque(i32){}; + try deque.init(std.heap.page_allocator); + defer deque.deinit(); + try deque.pushLast(3); + try deque.pushLast(2); + try deque.pushLast(5); + std.debug.print("両端キュー deque = ", .{}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + + // 要素にアクセス + var peek_first = deque.peekFirst(); + std.debug.print("\n先頭要素 peek_first = {}", .{peek_first}); + var peek_last = deque.peekLast(); + std.debug.print("\n末尾要素 peek_last = {}", .{peek_last}); + + // 要素をエンキュー + try deque.pushLast(4); + std.debug.print("\n要素 4 を末尾からエンキューした後 deque = ", .{}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + try deque.pushFirst(1); + std.debug.print("\n要素 1 を先頭からエンキューした後 deque = ", .{}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + + // 要素をデキュー + var pop_last = deque.popLast(); + std.debug.print("\n末尾から取り出した要素 = {},末尾から取り出した後 deque = ", .{pop_last}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + var pop_first = deque.popFirst(); + std.debug.print("\n先頭から取り出した要素 = {},先頭から取り出した後 deque = ", .{pop_first}); + inc.PrintUtil.printArray(i32, try deque.toArray()); + + // 両端キューの長さを取得 + var size = deque.size(); + std.debug.print("\n両端キューの長さ size = {}", .{size}); + + // 両端キューが空かどうかを判定 + var is_empty = deque.isEmpty(); + std.debug.print("\n両端キューが空かどうか = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_stack_and_queue/linkedlist_queue.zig b/ja/codes/zig/chapter_stack_and_queue/linkedlist_queue.zig new file mode 100644 index 000000000..5d018a996 --- /dev/null +++ b/ja/codes/zig/chapter_stack_and_queue/linkedlist_queue.zig @@ -0,0 +1,127 @@ +// File: linkedlist_queue.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 連結リストベースのキュー +pub fn LinkedListQueue(comptime T: type) type { + return struct { + const Self = @This(); + + front: ?*inc.ListNode(T) = null, // 先頭ノード front + rear: ?*inc.ListNode(T) = null, // 末尾ノード rear + que_size: usize = 0, // キューの長さ + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // メモリアロケータ + + // コンストラクタ(メモリ確保 + キューを初期化) + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.front = null; + self.rear = null; + self.que_size = 0; + } + + // デストラクタ(メモリを解放する) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // キューの長さを取得 + pub fn size(self: *Self) usize { + return self.que_size; + } + + // キューが空かどうかを判定 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // キュー先頭の要素にアクセス + pub fn peek(self: *Self) T { + if (self.size() == 0) @panic("キューが空です"); + return self.front.?.val; + } + + // エンキュー + pub fn push(self: *Self, num: T) !void { + // 末尾ノードの後ろに num を追加 + var node = try self.mem_allocator.create(inc.ListNode(T)); + node.init(num); + // キューが空なら、先頭・末尾ノードをともにそのノードに設定 + if (self.front == null) { + self.front = node; + self.rear = node; + // キューが空でなければ、そのノードを末尾ノードの後ろに追加 + } else { + self.rear.?.next = node; + self.rear = node; + } + self.que_size += 1; + } + + // デキュー + pub fn pop(self: *Self) T { + var num = self.peek(); + // 先頭ノードを削除 + self.front = self.front.?.next; + self.que_size -= 1; + return num; + } + + // 連結リストを配列に変換 + pub fn toArray(self: *Self) ![]T { + var node = self.front; + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + while (i < res.len) : (i += 1) { + res[i] = node.?.val; + node = node.?.next; + } + return res; + } + }; +} + +// Driver Code +pub fn main() !void { + // キューを初期化 + var queue = LinkedListQueue(i32){}; + try queue.init(std.heap.page_allocator); + defer queue.deinit(); + + // 要素をエンキュー + try queue.push(1); + try queue.push(3); + try queue.push(2); + try queue.push(5); + try queue.push(4); + std.debug.print("キュー queue = ", .{}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + + // キュー先頭の要素にアクセス + var peek = queue.peek(); + std.debug.print("\n先頭要素 peek = {}", .{peek}); + + // 要素をデキュー + var pop = queue.pop(); + std.debug.print("\nデキューした要素 pop = {}、デキュー後 queue = ", .{pop}); + inc.PrintUtil.printArray(i32, try queue.toArray()); + + // キューの長さを取得 + var size = queue.size(); + std.debug.print("\nキューの長さ size = {}", .{size}); + + // キューが空かどうかを判定 + var is_empty = queue.isEmpty(); + std.debug.print("\nキューが空かどうか = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_stack_and_queue/linkedlist_stack.zig b/ja/codes/zig/chapter_stack_and_queue/linkedlist_stack.zig new file mode 100644 index 000000000..fac15708b --- /dev/null +++ b/ja/codes/zig/chapter_stack_and_queue/linkedlist_stack.zig @@ -0,0 +1,118 @@ +// File: linkedlist_stack.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 連結リストベースのスタック +pub fn LinkedListStack(comptime T: type) type { + return struct { + const Self = @This(); + + stack_top: ?*inc.ListNode(T) = null, // 先頭ノードをスタックトップとする + stk_size: usize = 0, // スタックの長さ + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // メモリアロケータ + + // コンストラクタ(メモリを確保してスタックを初期化) + pub fn init(self: *Self, allocator: std.mem.Allocator) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + self.stack_top = null; + self.stk_size = 0; + } + + // デストラクタ(メモリを解放する) + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // スタックの長さを取得 + pub fn size(self: *Self) usize { + return self.stk_size; + } + + // スタックが空かどうかを判定 + pub fn isEmpty(self: *Self) bool { + return self.size() == 0; + } + + // スタックトップの要素にアクセス + pub fn peek(self: *Self) T { + if (self.size() == 0) @panic("スタックが空です"); + return self.stack_top.?.val; + } + + // プッシュ + pub fn push(self: *Self, num: T) !void { + var node = try self.mem_allocator.create(inc.ListNode(T)); + node.init(num); + node.next = self.stack_top; + self.stack_top = node; + self.stk_size += 1; + } + + // ポップ + pub fn pop(self: *Self) T { + var num = self.peek(); + self.stack_top = self.stack_top.?.next; + self.stk_size -= 1; + return num; + } + + // スタックを配列に変換 + pub fn toArray(self: *Self) ![]T { + var node = self.stack_top; + var res = try self.mem_allocator.alloc(T, self.size()); + @memset(res, @as(T, 0)); + var i: usize = 0; + while (i < res.len) : (i += 1) { + res[res.len - i - 1] = node.?.val; + node = node.?.next; + } + return res; + } + }; +} + +// Driver Code +pub fn main() !void { + // スタックを初期化 + var stack = LinkedListStack(i32){}; + try stack.init(std.heap.page_allocator); + // メモリの遅延解放 + defer stack.deinit(); + + // 要素をプッシュ + try stack.push(1); + try stack.push(3); + try stack.push(2); + try stack.push(5); + try stack.push(4); + std.debug.print("スタック stack = ", .{}); + inc.PrintUtil.printArray(i32, try stack.toArray()); + + // スタックトップの要素にアクセス + var peek = stack.peek(); + std.debug.print("\nスタック頂部の要素 top = {}", .{peek}); + + // 要素をポップ + var pop = stack.pop(); + std.debug.print("\nポップした要素 pop = {},ポップ後の stack = ", .{pop}); + inc.PrintUtil.printArray(i32, try stack.toArray()); + + // スタックの長さを取得 + var size = stack.size(); + std.debug.print("\nスタックの長さ size = {}", .{size}); + + // スタックが空かどうかを判定 + var is_empty = stack.isEmpty(); + std.debug.print("\nスタックが空かどうか = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/ja/codes/zig/chapter_stack_and_queue/queue.zig b/ja/codes/zig/chapter_stack_and_queue/queue.zig new file mode 100644 index 000000000..8628c4b4b --- /dev/null +++ b/ja/codes/zig/chapter_stack_and_queue/queue.zig @@ -0,0 +1,46 @@ +// File: queue.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // キューを初期化 + const L = std.TailQueue(i32); + var queue = L{}; + + // 要素をエンキュー + var node1 = L.Node{ .data = 1 }; + var node2 = L.Node{ .data = 3 }; + var node3 = L.Node{ .data = 2 }; + var node4 = L.Node{ .data = 5 }; + var node5 = L.Node{ .data = 4 }; + queue.append(&node1); + queue.append(&node2); + queue.append(&node3); + queue.append(&node4); + queue.append(&node5); + std.debug.print("キュー queue = ", .{}); + inc.PrintUtil.printQueue(i32, queue); + + // キュー先頭の要素にアクセス + var peek = queue.first.?.data; + std.debug.print("\n先頭要素 peek = {}", .{peek}); + + // 要素をデキュー + var pop = queue.popFirst().?.data; + std.debug.print("\nデキューした要素 pop = {}、デキュー後 queue = ", .{pop}); + inc.PrintUtil.printQueue(i32, queue); + + // キューの長さを取得 + var size = queue.len; + std.debug.print("\nキューの長さ size = {}", .{size}); + + // キューが空かどうかを判定 + var is_empty = if (queue.len == 0) true else false; + std.debug.print("\nキューが空かどうか = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_stack_and_queue/stack.zig b/ja/codes/zig/chapter_stack_and_queue/stack.zig new file mode 100644 index 000000000..1068aa50a --- /dev/null +++ b/ja/codes/zig/chapter_stack_and_queue/stack.zig @@ -0,0 +1,43 @@ +// File: stack.zig +// Created Time: 2023-01-08 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // スタックを初期化する + // zig では、ArrayList をスタックとして使うことが推奨される + var stack = std.ArrayList(i32).init(std.heap.page_allocator); + // メモリの遅延解放 + defer stack.deinit(); + + // 要素をプッシュ + try stack.append(1); + try stack.append(3); + try stack.append(2); + try stack.append(5); + try stack.append(4); + std.debug.print("スタック stack = ", .{}); + inc.PrintUtil.printList(i32, stack); + + // スタックトップの要素にアクセス + var peek = stack.items[stack.items.len - 1]; + std.debug.print("\nスタックトップ要素 peek = {}", .{peek}); + + // 要素をポップ + var pop = stack.pop(); + std.debug.print("\nポップした要素 pop = {},ポップ後の stack = ", .{pop}); + inc.PrintUtil.printList(i32, stack); + + // スタックの長さを取得 + var size = stack.items.len; + std.debug.print("\nスタックの長さ size = {}", .{size}); + + // スタックが空かどうかを判定 + var is_empty = if (stack.items.len == 0) true else false; + std.debug.print("\nスタックが空かどうか = {}", .{is_empty}); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_tree/avl_tree.zig b/ja/codes/zig/chapter_tree/avl_tree.zig new file mode 100644 index 000000000..5c62db73d --- /dev/null +++ b/ja/codes/zig/chapter_tree/avl_tree.zig @@ -0,0 +1,249 @@ +// File: avl_tree.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// AVL 木 +pub fn AVLTree(comptime T: type) type { + return struct { + const Self = @This(); + + root: ?*inc.TreeNode(T) = null, // 根ノード + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // メモリアロケータ + + // コンストラクタ + pub fn init(self: *Self, allocator: std.mem.Allocator) void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + } + + // デストラクタメソッド + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // ノードの高さを取得 + fn height(self: *Self, node: ?*inc.TreeNode(T)) i32 { + _ = self; + // 空ノードの高さは -1、葉ノードの高さは 0 + return if (node == null) -1 else node.?.height; + } + + // ノードの高さを更新する + fn updateHeight(self: *Self, node: ?*inc.TreeNode(T)) void { + // ノードの高さは最も高い部分木の高さ + 1 に等しい + node.?.height = @max(self.height(node.?.left), self.height(node.?.right)) + 1; + } + + // 平衡係数を取得 + fn balanceFactor(self: *Self, node: ?*inc.TreeNode(T)) i32 { + // 空ノードの平衡係数は 0 + if (node == null) return 0; + // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ + return self.height(node.?.left) - self.height(node.?.right); + } + + // 右回転 + fn rightRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + var child = node.?.left; + var grandChild = child.?.right; + // child を支点として node を右回転させる + child.?.right = node; + node.?.left = grandChild; + // ノードの高さを更新する + self.updateHeight(node); + self.updateHeight(child); + // 回転後の部分木の根ノードを返す + return child; + } + + // 左回転 + fn leftRotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + var child = node.?.right; + var grandChild = child.?.left; + // child を支点として node を左回転させる + child.?.left = node; + node.?.right = grandChild; + // ノードの高さを更新する + self.updateHeight(node); + self.updateHeight(child); + // 回転後の部分木の根ノードを返す + return child; + } + + // 回転操作を行い、この部分木の平衡を回復する + fn rotate(self: *Self, node: ?*inc.TreeNode(T)) ?*inc.TreeNode(T) { + // ノード node の平衡係数を取得 + var balance_factor = self.balanceFactor(node); + // 左に偏った木 + if (balance_factor > 1) { + if (self.balanceFactor(node.?.left) >= 0) { + // 右回転 + return self.rightRotate(node); + } else { + // 左回転してから右回転 + node.?.left = self.leftRotate(node.?.left); + return self.rightRotate(node); + } + } + // 右に偏った木 + if (balance_factor < -1) { + if (self.balanceFactor(node.?.right) <= 0) { + // 左回転 + return self.leftRotate(node); + } else { + // 右回転してから左回転 + node.?.right = self.rightRotate(node.?.right); + return self.leftRotate(node); + } + } + // 平衡木なので回転不要、そのまま返す + return node; + } + + // ノードを挿入 + fn insert(self: *Self, val: T) !void { + self.root = (try self.insertHelper(self.root, val)).?; + } + + // ノードを再帰的に挿入する(補助メソッド) + fn insertHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) !?*inc.TreeNode(T) { + var node = node_; + if (node == null) { + var tmp_node = try self.mem_allocator.create(inc.TreeNode(T)); + tmp_node.init(val); + return tmp_node; + } + // 1. 挿入位置を探索してノードを挿入 + if (val < node.?.val) { + node.?.left = try self.insertHelper(node.?.left, val); + } else if (val > node.?.val) { + node.?.right = try self.insertHelper(node.?.right, val); + } else { + return node; // 重複ノードは挿入せず、そのまま返す + } + self.updateHeight(node); // ノードの高さを更新する + // 2. 回転操作を行い、部分木の平衡を回復する + node = self.rotate(node); + // 部分木の根ノードを返す + return node; + } + + // ノードを削除 + fn remove(self: *Self, val: T) void { + self.root = self.removeHelper(self.root, val).?; + } + + // ノードを再帰的に削除する(補助メソッド) + fn removeHelper(self: *Self, node_: ?*inc.TreeNode(T), val: T) ?*inc.TreeNode(T) { + var node = node_; + if (node == null) return null; + // 1. ノードを探索して削除 + if (val < node.?.val) { + node.?.left = self.removeHelper(node.?.left, val); + } else if (val > node.?.val) { + node.?.right = self.removeHelper(node.?.right, val); + } else { + if (node.?.left == null or node.?.right == null) { + var child = if (node.?.left != null) node.?.left else node.?.right; + // 子ノード数 = 0 の場合、node をそのまま削除して返す + if (child == null) { + return null; + // 子ノード数 = 1 の場合、node をそのまま削除する + } else { + node = child; + } + } else { + // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える + var temp = node.?.right; + while (temp.?.left != null) { + temp = temp.?.left; + } + node.?.right = self.removeHelper(node.?.right, temp.?.val); + node.?.val = temp.?.val; + } + } + self.updateHeight(node); // ノードの高さを更新する + // 2. 回転操作を行い、部分木の平衡を回復する + node = self.rotate(node); + // 部分木の根ノードを返す + return node; + } + + // ノードを探索 + fn search(self: *Self, val: T) ?*inc.TreeNode(T) { + var cur = self.root; + // ループで探索し、葉ノードを越えたら抜ける + while (cur != null) { + // 目標ノードは cur の右部分木にある + if (cur.?.val < val) { + cur = cur.?.right; + // 目標ノードは cur の左部分木にある + } else if (cur.?.val > val) { + cur = cur.?.left; + // 目標ノードが見つかったらループを抜ける + } else { + break; + } + } + // 目標ノードを返す + return cur; + } + }; +} + +pub fn testInsert(comptime T: type, tree_: *AVLTree(T), val: T) !void { + var tree = tree_; + try tree.insert(val); + std.debug.print("\nノード {} を挿入した後,AVL 木は\n", .{val}); + try inc.PrintUtil.printTree(tree.root, null, false); +} + +pub fn testRemove(comptime T: type, tree_: *AVLTree(T), val: T) void { + var tree = tree_; + tree.remove(val); + std.debug.print("\nノード {} を削除した後,AVL 木は\n", .{val}); + try inc.PrintUtil.printTree(tree.root, null, false); +} + +// Driver Code +pub fn main() !void { + // 空の AVL 木を初期化する + var avl_tree = AVLTree(i32){}; + avl_tree.init(std.heap.page_allocator); + defer avl_tree.deinit(); + + // ノードを挿入する + // ノード挿入後に AVL 木がどのように平衡を保つかに注目 + try testInsert(i32, &avl_tree, 1); + try testInsert(i32, &avl_tree, 2); + try testInsert(i32, &avl_tree, 3); + try testInsert(i32, &avl_tree, 4); + try testInsert(i32, &avl_tree, 5); + try testInsert(i32, &avl_tree, 8); + try testInsert(i32, &avl_tree, 7); + try testInsert(i32, &avl_tree, 9); + try testInsert(i32, &avl_tree, 10); + try testInsert(i32, &avl_tree, 6); + + // 重複ノードを挿入する + try testInsert(i32, &avl_tree, 7); + + // ノードを削除する + // ノード削除後に AVL 木がどのように平衡を保つかに注目 + testRemove(i32, &avl_tree, 8); // 次数 0 のノードを削除する + testRemove(i32, &avl_tree, 5); // 次数 1 のノードを削除する + testRemove(i32, &avl_tree, 4); // 次数 2 のノードを削除する + + // ノードを探索 + var node = avl_tree.search(7).?; + std.debug.print("\n見つかったノードオブジェクトは {any},ノードの値 = {}\n", .{node, node.val}); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/ja/codes/zig/chapter_tree/binary_search_tree.zig b/ja/codes/zig/chapter_tree/binary_search_tree.zig new file mode 100644 index 000000000..a93518e2f --- /dev/null +++ b/ja/codes/zig/chapter_tree/binary_search_tree.zig @@ -0,0 +1,182 @@ +// File: binary_search_tree.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// 二分探索木 +pub fn BinarySearchTree(comptime T: type) type { + return struct { + const Self = @This(); + + root: ?*inc.TreeNode(T) = null, + mem_arena: ?std.heap.ArenaAllocator = null, + mem_allocator: std.mem.Allocator = undefined, // メモリアロケータ + + // コンストラクタ + pub fn init(self: *Self, allocator: std.mem.Allocator, nums: []T) !void { + if (self.mem_arena == null) { + self.mem_arena = std.heap.ArenaAllocator.init(allocator); + self.mem_allocator = self.mem_arena.?.allocator(); + } + std.mem.sort(T, nums, {}, comptime std.sort.asc(T)); // 配列をソート + self.root = try self.buildTree(nums, 0, nums.len - 1); // 二分探索木を構築 + } + + // デストラクタメソッド + pub fn deinit(self: *Self) void { + if (self.mem_arena == null) return; + self.mem_arena.?.deinit(); + } + + // 二分探索木を構築 + fn buildTree(self: *Self, nums: []T, i: usize, j: usize) !?*inc.TreeNode(T) { + if (i > j) return null; + // 配列の中央ノードを根ノードとする + var mid = i + (j - i) / 2; + var node = try self.mem_allocator.create(inc.TreeNode(T)); + node.init(nums[mid]); + // 左部分木と右部分木を再帰的に構築する + if (mid >= 1) node.left = try self.buildTree(nums, i, mid - 1); + node.right = try self.buildTree(nums, mid + 1, j); + return node; + } + + // 二分木の根ノードを取得 + fn getRoot(self: *Self) ?*inc.TreeNode(T) { + return self.root; + } + + // ノードを探索 + fn search(self: *Self, num: T) ?*inc.TreeNode(T) { + var cur = self.root; + // ループで探索し、葉ノードを越えたら抜ける + while (cur != null) { + // 目標ノードは cur の右部分木にある + if (cur.?.val < num) { + cur = cur.?.right; + // 目標ノードは cur の左部分木にある + } else if (cur.?.val > num) { + cur = cur.?.left; + // 目標ノードが見つかったらループを抜ける + } else { + break; + } + } + // 目標ノードを返す + return cur; + } + + // ノードを挿入 + fn insert(self: *Self, num: T) !void { + // 木が空なら、根ノードを初期化する + if (self.root == null) { + self.root = try self.mem_allocator.create(inc.TreeNode(T)); + return; + } + var cur = self.root; + var pre: ?*inc.TreeNode(T) = null; + // ループで探索し、葉ノードを越えたら抜ける + while (cur != null) { + // 重複ノードが見つかったら、直ちに返す + if (cur.?.val == num) return; + pre = cur; + // 挿入位置は cur の右部分木にある + if (cur.?.val < num) { + cur = cur.?.right; + // 挿入位置は cur の左部分木にある + } else { + cur = cur.?.left; + } + } + // ノードを挿入 + var node = try self.mem_allocator.create(inc.TreeNode(T)); + node.init(num); + if (pre.?.val < num) { + pre.?.right = node; + } else { + pre.?.left = node; + } + } + + // ノードを削除 + fn remove(self: *Self, num: T) void { + // 木が空なら、そのまま早期リターンする + if (self.root == null) return; + var cur = self.root; + var pre: ?*inc.TreeNode(T) = null; + // ループで探索し、葉ノードを越えたら抜ける + while (cur != null) { + // 削除対象のノードが見つかったら、ループを抜ける + if (cur.?.val == num) break; + pre = cur; + // 削除対象ノードは cur の右部分木にある + if (cur.?.val < num) { + cur = cur.?.right; + // 削除対象ノードは cur の左部分木にある + } else { + cur = cur.?.left; + } + } + // 削除対象ノードがなければそのまま返す + if (cur == null) return; + // 子ノード数 = 0 or 1 + if (cur.?.left == null or cur.?.right == null) { + // 子ノード数が 0 / 1 のとき、child = null / その子ノード + var child = if (cur.?.left != null) cur.?.left else cur.?.right; + // ノード cur を削除する + if (pre.?.left == cur) { + pre.?.left = child; + } else { + pre.?.right = child; + } + // 子ノード数 = 2 + } else { + // 中順走査における cur の次ノードを取得 + var tmp = cur.?.right; + while (tmp.?.left != null) { + tmp = tmp.?.left; + } + var tmp_val = tmp.?.val; + // ノード tmp を再帰的に削除 + self.remove(tmp.?.val); + // tmp で cur を上書きする + cur.?.val = tmp_val; + } + } + }; +} + +// Driver Code +pub fn main() !void { + // 二分木を初期化 + var nums = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + var bst = BinarySearchTree(i32){}; + try bst.init(std.heap.page_allocator, &nums); + defer bst.deinit(); + std.debug.print("初期化された二分木は\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + + // ノードを探索 + var node = bst.search(7); + std.debug.print("\n見つかったノードオブジェクトは {any},ノードの値 = {}\n", .{node, node.?.val}); + + // ノードを挿入 + try bst.insert(16); + std.debug.print("\nノード 16 を挿入した後,二分木は\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + + // ノードを削除 + bst.remove(1); + std.debug.print("\nノード 1 を削除した後,二分木は\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + bst.remove(2); + std.debug.print("\nノード 2 を削除した後,二分木は\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + bst.remove(4); + std.debug.print("\nノード 4 を削除した後,二分木は\n", .{}); + try inc.PrintUtil.printTree(bst.getRoot(), null, false); + + _ = try std.io.getStdIn().reader().readByte(); +} diff --git a/ja/codes/zig/chapter_tree/binary_tree.zig b/ja/codes/zig/chapter_tree/binary_tree.zig new file mode 100644 index 000000000..6b8998cbd --- /dev/null +++ b/ja/codes/zig/chapter_tree/binary_tree.zig @@ -0,0 +1,39 @@ +// File: binary_tree.zig +// Created Time: 2023-01-14 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// Driver Code +pub fn main() !void { + // 二分木を初期化する + // ノードを初期化する + var n1 = inc.TreeNode(i32){ .val = 1 }; + var n2 = inc.TreeNode(i32){ .val = 2 }; + var n3 = inc.TreeNode(i32){ .val = 3 }; + var n4 = inc.TreeNode(i32){ .val = 4 }; + var n5 = inc.TreeNode(i32){ .val = 5 }; + // ノード間の参照(ポインタ)を構築する + n1.left = &n2; + n1.right = &n3; + n2.left = &n4; + n2.right = &n5; + std.debug.print("二分木を初期化\n", .{}); + try inc.PrintUtil.printTree(&n1, null, false); + + // ノードの挿入と削除 + var p = inc.TreeNode(i32){ .val = 0 }; + // n1 -> n2 の間にノード P を挿入 + n1.left = &p; + p.left = &n2; + std.debug.print("ノード P を挿入した後\n", .{}); + try inc.PrintUtil.printTree(&n1, null, false); + // ノードを削除 + n1.left = &n2; + std.debug.print("ノード P を削除した後\n", .{}); + try inc.PrintUtil.printTree(&n1, null, false); + + _ = try std.io.getStdIn().reader().readByte(); +} + diff --git a/ja/codes/zig/chapter_tree/binary_tree_bfs.zig b/ja/codes/zig/chapter_tree/binary_tree_bfs.zig new file mode 100644 index 000000000..3800bc169 --- /dev/null +++ b/ja/codes/zig/chapter_tree/binary_tree_bfs.zig @@ -0,0 +1,57 @@ +// File: binary_tree_bfs.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +// レベル順走査 +fn levelOrder(comptime T: type, mem_allocator: std.mem.Allocator, root: *inc.TreeNode(T)) !std.ArrayList(T) { + // キューを初期化し、ルートノードを追加する + const L = std.TailQueue(*inc.TreeNode(T)); + var queue = L{}; + var root_node = try mem_allocator.create(L.Node); + root_node.data = root; + queue.append(root_node); + // 走査順序を保存するためのリストを初期化する + var list = std.ArrayList(T).init(std.heap.page_allocator); + while (queue.len > 0) { + var queue_node = queue.popFirst().?; // デキュー + var node = queue_node.data; + try list.append(node.val); // ノードの値を保存する + if (node.left != null) { + var tmp_node = try mem_allocator.create(L.Node); + tmp_node.data = node.left.?; + queue.append(tmp_node); // 左子ノードをキューに追加 + } + if (node.right != null) { + var tmp_node = try mem_allocator.create(L.Node); + tmp_node.data = node.right.?; + queue.append(tmp_node); // 右子ノードをキューに追加 + } + } + return list; +} + +// Driver Code +pub fn main() !void { + // メモリアロケータを初期化する + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // 二分木を初期化 + // ここでは、配列から直接二分木を生成する関数を利用する + var nums = [_]i32{1, 2, 3, 4, 5, 6, 7}; + var root = try inc.TreeUtil.arrToTree(i32, mem_allocator, &nums); + std.debug.print("二分木を初期化\n", .{}); + try inc.PrintUtil.printTree(root, null, false); + + // レベル順走査 + var list = try levelOrder(i32, mem_allocator, root.?); + defer list.deinit(); + std.debug.print("\nレベル順走査のノード出力シーケンス = ", .{}); + inc.PrintUtil.printList(i32, list); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/ja/codes/zig/chapter_tree/binary_tree_dfs.zig b/ja/codes/zig/chapter_tree/binary_tree_dfs.zig new file mode 100644 index 000000000..dd112f9b8 --- /dev/null +++ b/ja/codes/zig/chapter_tree/binary_tree_dfs.zig @@ -0,0 +1,70 @@ +// File: binary_tree_dfs.zig +// Created Time: 2023-01-15 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +const inc = @import("include"); + +var list = std.ArrayList(i32).init(std.heap.page_allocator); + +// 先行順走査 +fn preOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // 訪問順序:根ノード -> 左部分木 -> 右部分木 + try list.append(root.?.val); + try preOrder(T, root.?.left); + try preOrder(T, root.?.right); +} + +// 中順走査 +fn inOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // 訪問優先順: 左部分木 -> 根ノード -> 右部分木 + try inOrder(T, root.?.left); + try list.append(root.?.val); + try inOrder(T, root.?.right); +} + +// 後順走査 +fn postOrder(comptime T: type, root: ?*inc.TreeNode(T)) !void { + if (root == null) return; + // 訪問優先順: 左部分木 -> 右部分木 -> 根ノード + try postOrder(T, root.?.left); + try postOrder(T, root.?.right); + try list.append(root.?.val); +} + +// Driver Code +pub fn main() !void { + // メモリアロケータを初期化する + var mem_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer mem_arena.deinit(); + const mem_allocator = mem_arena.allocator(); + + // 二分木を初期化 + // ここでは、配列から直接二分木を生成する関数を利用する + var nums = [_]i32{1, 2, 3, 4, 5, 6, 7}; + var root = try inc.TreeUtil.arrToTree(i32, mem_allocator, &nums); + std.debug.print("二分木を初期化\n", .{}); + try inc.PrintUtil.printTree(root, null, false); + + // 先行順走査 + list.clearRetainingCapacity(); + try preOrder(i32, root); + std.debug.print("\n前順走査のノード出力シーケンス = ", .{}); + inc.PrintUtil.printList(i32, list); + + // 中順走査 + list.clearRetainingCapacity(); + try inOrder(i32, root); + std.debug.print("\n中順走査のノード出力シーケンス = ", .{}); + inc.PrintUtil.printList(i32, list); + + // 後順走査 + list.clearRetainingCapacity(); + try postOrder(i32, root); + std.debug.print("\n後順走査のノード出力シーケンス = ", .{}); + inc.PrintUtil.printList(i32, list); + + _ = try std.io.getStdIn().reader().readByte(); +} \ No newline at end of file diff --git a/ja/codes/zig/include/PrintUtil.zig b/ja/codes/zig/include/PrintUtil.zig new file mode 100644 index 000000000..d038d93b4 --- /dev/null +++ b/ja/codes/zig/include/PrintUtil.zig @@ -0,0 +1,42 @@ +// File: PrintUtil.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +const std = @import("std"); +pub const ListUtil = @import("ListNode.zig"); +pub const ListNode = ListUtil.ListNode; +pub const TreeUtil = @import("TreeNode.zig"); +pub const TreeNode = TreeUtil.TreeNode; + +// キューを出力する +pub fn printQueue(comptime T: type, queue: std.TailQueue(T)) void { + var node = queue.first; + std.debug.print("[", .{}); + var i: i32 = 0; + while (node != null) : (i += 1) { + var data = node.?.data; + std.debug.print("{}{s}", .{ data, if (i == queue.len - 1) "]" else ", " }); + node = node.?.next; + } +} + +// ハッシュテーブルを出力 +pub fn printHashMap(comptime TKey: type, comptime TValue: type, map: std.AutoHashMap(TKey, TValue)) void { + var it = map.iterator(); + while (it.next()) |kv| { + var key = kv.key_ptr.*; + var value = kv.value_ptr.*; + std.debug.print("{} -> {s}\n", .{ key, value }); + } +} + +// ヒープを出力 +pub fn printHeap(comptime T: type, mem_allocator: std.mem.Allocator, queue: anytype) !void { + var arr = queue.items; + var len = queue.len; + std.debug.print("ヒープの配列表現:", .{}); + printArray(T, arr[0..len]); + std.debug.print("\nヒープの木構造表現:\n", .{}); + var root = try TreeUtil.arrToTree(T, mem_allocator, arr[0..len]); + try printTree(root, null, false); +} diff --git a/ja/codes/zig/include/include.zig b/ja/codes/zig/include/include.zig new file mode 100644 index 000000000..4c4ec8369 --- /dev/null +++ b/ja/codes/zig/include/include.zig @@ -0,0 +1,7 @@ +// File: include.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com) + +pub const PrintUtil = @import("PrintUtil.zig"); +pub const TreeUtil = @import("TreeNode.zig"); +pub const TreeNode = TreeUtil.TreeNode; diff --git a/ja/codes/zig/main.zig b/ja/codes/zig/main.zig new file mode 100644 index 000000000..970449d97 --- /dev/null +++ b/ja/codes/zig/main.zig @@ -0,0 +1,25 @@ +const std = @import("std"); + +const iteration = @import("chapter_computational_complexity/iteration.zig"); +const recursion = @import("chapter_computational_complexity/recursion.zig"); +const time_complexity = @import("chapter_computational_complexity/time_complexity.zig"); +const space_complexity = @import("chapter_computational_complexity/space_complexity.zig"); +const worst_best_time_complexity = @import("chapter_computational_complexity/worst_best_time_complexity.zig"); + +const array = @import("chapter_array_and_linkedlist/array.zig"); +const linked_list = @import("chapter_array_and_linkedlist/linked_list.zig"); +const list = @import("chapter_array_and_linkedlist/list.zig"); +const my_list = @import("chapter_array_and_linkedlist/my_list.zig"); + +pub fn main() !void { + try iteration.run(); + recursion.run(); + time_complexity.run(); + try space_complexity.run(); + worst_best_time_complexity.run(); + + try array.run(); + linked_list.run(); + try list.run(); + try my_list.run(); +} diff --git a/ja/codes/zig/utils/ListNode.zig b/ja/codes/zig/utils/ListNode.zig new file mode 100644 index 000000000..0221e7c8d --- /dev/null +++ b/ja/codes/zig/utils/ListNode.zig @@ -0,0 +1,49 @@ +// File: ListNode.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); + +// 連結リストノード +pub fn ListNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = 0, + next: ?*Self = null, + + // Initialize a list node with specific value + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.next = null; + } + }; +} + +// リストを連結リストにデシリアライズする +pub fn listToLinkedList(comptime T: type, allocator: std.mem.Allocator, list: std.ArrayList(T)) !?*ListNode(T) { + var dum = try allocator.create(ListNode(T)); + dum.init(0); + var head = dum; + for (list.items) |val| { + var tmp = try allocator.create(ListNode(T)); + tmp.init(val); + head.next = tmp; + head = head.next.?; + } + return dum.next; +} + +// 配列をデシリアライズして連結リストに変換する +pub fn arrToLinkedList(comptime T: type, mem_allocator: std.mem.Allocator, arr: []T) !?*ListNode(T) { + var dum = try mem_allocator.create(ListNode(T)); + dum.init(0); + var head = dum; + for (arr) |val| { + var tmp = try mem_allocator.create(ListNode(T)); + tmp.init(val); + head.next = tmp; + head = head.next.?; + } + return dum.next; +} diff --git a/ja/codes/zig/utils/TreeNode.zig b/ja/codes/zig/utils/TreeNode.zig new file mode 100644 index 000000000..ae6d31239 --- /dev/null +++ b/ja/codes/zig/utils/TreeNode.zig @@ -0,0 +1,63 @@ +// File: TreeNode.zig +// Created Time: 2023-01-07 +// Author: codingonion (coderonion@gmail.com), CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); + +// 二分木ノード +pub fn TreeNode(comptime T: type) type { + return struct { + const Self = @This(); + + val: T = undefined, // ノード値 + height: i32 = undefined, // ノードの高さ + left: ?*Self = null, // 左の子ノードへのポインタ + right: ?*Self = null, // 右の子ノードへのポインタ + + // Initialize a tree node with specific value + pub fn init(self: *Self, x: i32) void { + self.val = x; + self.height = 0; + self.left = null; + self.right = null; + } + }; +} + +// 配列をデシリアライズして二分木に変換する +pub fn arrToTree(comptime T: type, allocator: std.mem.Allocator, arr: []T) !?*TreeNode(T) { + if (arr.len == 0) return null; + var root = try allocator.create(TreeNode(T)); + root.init(arr[0]); + const L = std.TailQueue(*TreeNode(T)); + var que = L{}; + var root_node = try allocator.create(L.Node); + root_node.data = root; + que.append(root_node); + var index: usize = 0; + while (que.len > 0) { + const que_node = que.popFirst().?; + var node = que_node.data; + index += 1; + if (index >= arr.len) break; + if (index < arr.len) { + var tmp = try allocator.create(TreeNode(T)); + tmp.init(arr[index]); + node.left = tmp; + var tmp_node = try allocator.create(L.Node); + tmp_node.data = node.left.?; + que.append(tmp_node); + } + index += 1; + if (index >= arr.len) break; + if (index < arr.len) { + var tmp = try allocator.create(TreeNode(T)); + tmp.init(arr[index]); + node.right = tmp; + var tmp_node = try allocator.create(L.Node); + tmp_node.data = node.right.?; + que.append(tmp_node); + } + } + return root; +} diff --git a/ja/codes/zig/utils/format.zig b/ja/codes/zig/utils/format.zig new file mode 100644 index 000000000..49b3895d0 --- /dev/null +++ b/ja/codes/zig/utils/format.zig @@ -0,0 +1,140 @@ +// File: format.zig +// Created Time: 2025-07-19 +// Author: CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); +const ListNode = @import("ListNode.zig").ListNode; +const TreeNode = @import("TreeNode.zig").TreeNode; + +pub fn slice(items: anytype) SliceFormatter(@TypeOf(items)) { + return .{ .items = items }; +} + +pub fn SliceFormatter(comptime SliceType: type) type { + return struct { + const Self = @This(); + + items: SliceType, + + pub fn format( + self: Self, + comptime _: []const u8, + _: std.fmt.FormatOptions, + writer: anytype, + ) !void { + try writer.writeAll("["); + + if (self.items.len > 0) { + for (self.items, 0..) |item, i| { + try std.fmt.format(writer, "{}", .{item}); + if (i != self.items.len - 1) { + try writer.writeAll(", "); + } + } + } + + try writer.writeAll("]"); + } + }; +} + +pub fn linkedList(comptime T: type, head: *const ListNode(T)) LinkedListFormatter(T) { + return .{ .head = head }; +} + +pub fn LinkedListFormatter(comptime T: type) type { + return struct { + const Self = @This(); + + head: *const ListNode(T), + + pub fn format( + self: Self, + comptime _: []const u8, + _: std.fmt.FormatOptions, + writer: anytype, + ) !void { + try printLinkedList(self.head, writer); + } + + pub fn printLinkedList(head: *const ListNode(T), writer: anytype) !void { + try std.fmt.format(writer, "{}", .{head.val}); + if (head.next) |next_node| { + try writer.writeAll("->"); + try printLinkedList(next_node, writer); + } + } + }; +} + +pub fn tree(comptime T: type, root: ?*const TreeNode(T)) TreeFormatter(T) { + return .{ .root = root }; +} + +pub fn TreeFormatter(comptime T: type) type { + return struct { + const Self = @This(); + + root: ?*const TreeNode(T), + + pub fn format( + self: Self, + comptime _: []const u8, + _: std.fmt.FormatOptions, + writer: anytype, + ) !void { + try printTree(self.root, null, false, writer); + } + + // 二分木を出力 + fn printTree(root: ?*const TreeNode(T), prev: ?*Trunk, isRight: bool, writer: anytype) !void { + if (root == null) { + return; + } + + var prev_str = " "; + var trunk = Trunk{ .prev = prev, .str = prev_str }; + + try printTree(root.?.right, &trunk, true, writer); + + if (prev == null) { + trunk.str = "———"; + } else if (isRight) { + trunk.str = "/———"; + prev_str = " |"; + } else { + trunk.str = "\\———"; + prev.?.str = prev_str; + } + + try showTrunks(&trunk, writer); + try std.fmt.format(writer, "{d}\n", .{root.?.val}); + + if (prev) |_| { + prev.?.str = prev_str; + } + trunk.str = " |"; + + try printTree(root.?.left, &trunk, false, writer); + } + + // 二分木を出力 + // This tree printer is borrowed from TECHIE DELIGHT + // https://www.techiedelight.com/c-program-print-binary-tree/ + const Trunk = struct { + prev: ?*Trunk = null, + str: []const u8 = undefined, + + pub fn init(self: *Trunk, prev: ?*Trunk, str: []const u8) void { + self.prev = prev; + self.str = str; + } + }; + + pub fn showTrunks(p: ?*Trunk, writer: anytype) !void { + if (p == null) return; + try showTrunks(p.?.prev, writer); + try std.fmt.format(writer, "{s}", .{p.?.str}); + } + }; +} diff --git a/ja/codes/zig/utils/utils.zig b/ja/codes/zig/utils/utils.zig new file mode 100644 index 000000000..a3d53797c --- /dev/null +++ b/ja/codes/zig/utils/utils.zig @@ -0,0 +1,8 @@ +// File: format.zig +// Created Time: 2025-07-15 +// Author: CreatorMetaSky (creator_meta_sky@163.com) + +const std = @import("std"); +pub const fmt = @import("format.zig"); +pub const ListNode = @import("ListNode.zig").ListNode; +pub const TreeNode = @import("TreeNode.zig").TreeNode; diff --git a/ja/docs/chapter_appendix/contribution.md b/ja/docs/chapter_appendix/contribution.md index 53b14feec..f530f1725 100644 --- a/ja/docs/chapter_appendix/contribution.md +++ b/ja/docs/chapter_appendix/contribution.md @@ -1,46 +1,46 @@ -# コントリビューション +# 一緒に制作に参加しましょう -著者の能力に限りがあるため、本書にはいくつかの省略や誤りが避けられません。ご理解をお願いします。誤字、リンク切れ、内容の欠落、文章の曖昧さ、説明の不明確さ、または不合理な文章構造を発見された場合は、読者により良質な学習リソースを提供するため、修正にご協力ください。 +著者の力には限りがあるため、本書にはどうしても一部の漏れや誤りが含まれる可能性があります。ご了承ください。誤字、リンク切れ、内容の欠落、表現の曖昧さ、説明の不明瞭さ、文章構成の不適切さなどの問題を見つけた場合は、ぜひ修正にご協力ください。読者により良い学習リソースを提供できます。 -すべての[コントリビューター](https://github.com/krahets/hello-algo/graphs/contributors)のGitHub IDは、本書のリポジトリ、ウェブ、PDFバージョンのホームページに表示され、オープンソースコミュニティへの無私の貢献に感謝いたします。 +すべての[寄稿者](https://github.com/krahets/hello-algo/graphs/contributors)の GitHub ID は、本書のリポジトリ、Web 版、PDF 版のホームページに掲載され、オープンソースコミュニティへの惜しみない貢献に感謝を表します。 !!! success "オープンソースの魅力" - 紙の本の2つの印刷版の間隔はしばしば長く、内容の更新が非常に不便です。 - - しかし、このオープンソースの本では、内容の更新サイクルは数日、さらには数時間に短縮されます。 + 紙の書籍では、2 回の増刷の間隔が長くなりがちで、内容更新は非常に不便です。 + + 一方、このオープンソース書籍では、内容更新のサイクルは数日、場合によっては数時間にまで短縮されています。 ### 内容の微調整 -下の図に示すように、各ページの右上角に「編集アイコン」があります。以下の手順に従ってテキストやコードを修正できます。 +以下の図のように、各ページの右上には「編集アイコン」があります。次の手順で本文やコードを修正できます。 -1. 「編集アイコン」をクリックします。「このリポジトリをフォークしますか」と促された場合は、同意してください。 -2. Markdownソースファイルの内容を修正し、内容の正確性を確認し、フォーマットの一貫性を保つようにしてください。 -3. ページの下部で修正説明を記入し、「Propose file change」ボタンをクリックします。ページがリダイレクトされた後、「Create pull request」ボタンをクリックしてプルリクエストを開始します。 +1. 「編集アイコン」をクリックし、「このリポジトリを Fork する必要があります」と表示された場合は、その操作を承認してください。 +2. Markdown のソースファイルを修正し、内容が正しいことを確認したうえで、できるだけ書式の統一を保ってください。 +3. ページ下部に修正内容の説明を入力し、その後「Propose file change」ボタンをクリックします。ページ遷移後、「Create pull request」ボタンをクリックするとプルリクエストを作成できます。 ![ページ編集ボタン](contribution.assets/edit_markdown.png) -図は直接修正できないため、新しい[Issue](https://github.com/krahets/hello-algo/issues)を作成するか、問題を説明するコメントが必要です。できるだけ早く図を再描画して置き換えます。 +画像は直接修正できないため、新しい [Issue](https://github.com/krahets/hello-algo/issues) を作成するかコメントで問題を説明してください。できるだけ早く描き直して差し替えます。 -### 内容の作成 +### コンテンツ制作 -このオープンソースプロジェクトへの参加に興味がある場合、コードを他のプログラミング言語に翻訳したり、記事の内容を拡張したりすることを含めて、以下のプルリクエストワークフローを実装する必要があります。 +コードを他のプログラミング言語へ翻訳することや、記事内容を拡充することなど、このオープンソースプロジェクトへの参加に興味がある場合は、以下の Pull Request ワークフローに従ってください。 -1. GitHubにログインし、本書の[コードリポジトリ](https://github.com/krahets/hello-algo)を個人アカウントにフォークします。 -2. フォークしたリポジトリのウェブページに移動し、`git clone`コマンドを使用してリポジトリをローカルマシンにクローンします。 -3. ローカルで内容を作成し、完全なテストを実行してコードの正確性を検証します。 -4. ローカルで行った変更をコミットし、リモートリポジトリにプッシュします。 -5. リポジトリのウェブページを更新し、「Create pull request」ボタンをクリックしてプルリクエストを開始します。 +1. GitHub にログインし、本書の[コードリポジトリ](https://github.com/krahets/hello-algo)を個人アカウントに Fork します。 +2. Fork したリポジトリのページに入り、`git clone` コマンドを使ってリポジトリをローカルにクローンします。 +3. ローカルでコンテンツを作成し、完全なテストを行ってコードの正しさを確認します。 +4. ローカルで行った変更を Commit し、その後リモートリポジトリへ Push します。 +5. リポジトリのページを更新し、「Create pull request」ボタンをクリックするとプルリクエストを作成できます。 -### Dockerデプロイメント +### Docker デプロイ -`hello-algo`ルートディレクトリで、以下のDockerスクリプトを実行して`http://localhost:8000`でプロジェクトにアクセスします: +`hello-algo` のルートディレクトリで以下の Docker スクリプトを実行すると、`http://localhost:8000` で本プロジェクトにアクセスできます。 ```shell docker-compose up -d ``` -以下のコマンドを使用してデプロイメントを削除します: +以下のコマンドでデプロイを削除できます。 ```shell docker-compose down diff --git a/ja/docs/chapter_appendix/installation.md b/ja/docs/chapter_appendix/installation.md index 7143da803..d16901102 100644 --- a/ja/docs/chapter_appendix/installation.md +++ b/ja/docs/chapter_appendix/installation.md @@ -1,68 +1,68 @@ -# インストール +# プログラミング環境のインストール -## IDEのインストール +## IDE のインストール -ローカルの統合開発環境(IDE)として、オープンソースで軽量なVS Codeを使用することをお勧めします。[VS Code公式ウェブサイト](https://code.visualstudio.com/)にアクセスし、お使いのオペレーティングシステムに適したVS Codeのバージョンを選択してダウンロードし、インストールしてください。 +オープンソースで軽量な VS Code をローカルの統合開発環境(IDE)として使用することを推奨します。[VS Code 公式サイト](https://code.visualstudio.com/) にアクセスし、使用している OS に応じたバージョンの VS Code をダウンロードしてインストールしてください。 -![公式ウェブサイトからVS Codeをダウンロード](installation.assets/vscode_installation.png) +![公式サイトから VS Code をダウンロード](installation.assets/vscode_installation.png) -VS Codeには強力な拡張機能エコシステムがあり、ほとんどのプログラミング言語の実行とデバッグをサポートしています。例えば、「Python Extension Pack」をインストールした後、Pythonコードをデバッグできます。インストール手順を下の図に示します。 +VS Code には強力な拡張機能のエコシステムがあり、ほとんどのプログラミング言語の実行とデバッグをサポートしています。Python を例にすると、「Python Extension Pack」拡張機能をインストールした後、Python コードをデバッグできるようになります。インストール手順を以下に示します。 -![VS Code拡張機能パックのインストール](installation.assets/vscode_extension_installation.png) +![VS Code 拡張機能のインストール](installation.assets/vscode_extension_installation.png) ## 言語環境のインストール -### Python環境 +### Python 環境 -1. [Miniconda3](https://docs.conda.io/en/latest/miniconda.html)をダウンロードしてインストールします。Python 3.10以降が必要です。 -2. VS Code拡張機能マーケットプレイスで`python`を検索し、Python Extension Packをインストールします。 -3. (オプション)コマンドラインで`pip install black`を入力して、コードフォーマッティングツールをインストールします。 +1. [Miniconda3](https://docs.conda.io/en/latest/miniconda.html) をダウンロードしてインストールします。Python 3.10 以降が必要です。 +2. VS Code の拡張機能マーケットプレイスで `python` を検索し、Python Extension Pack をインストールします。 +3. (任意)コマンドラインで `pip install black` を入力し、コード整形ツールをインストールします。 -### C/C++環境 +### C/C++ 環境 -1. Windowsシステムでは[MinGW](https://sourceforge.net/projects/mingw-w64/files/)をインストールする必要があります([設定チュートリアル](https://blog.csdn.net/qq_33698226/article/details/129031241))。MacOSにはClangが付属しているため、インストールは不要です。 -2. VS Code拡張機能マーケットプレイスで`c++`を検索し、C/C++ Extension Packをインストールします。 -3. (オプション)設定ページを開き、`Clang_format_fallback Style`コードフォーマッティングオプションを検索し、`{ BasedOnStyle: Microsoft, BreakBeforeBraces: Attach }`に設定します。 +1. Windows システムでは [MinGW](https://sourceforge.net/projects/mingw-w64/files/) をインストールする必要があります([設定チュートリアル](https://blog.csdn.net/qq_33698226/article/details/129031241))。MacOS には Clang が標準搭載されているため、追加インストールは不要です。 +2. VS Code の拡張機能マーケットプレイスで `c++` を検索し、C/C++ Extension Pack をインストールします。 +3. (任意)Settings ページを開き、コード整形オプション `Clang_format_fallback Style` を検索して、`{ BasedOnStyle: Microsoft, BreakBeforeBraces: Attach }` に設定します。 -### Java環境 +### Java 環境 -1. [OpenJDK](https://jdk.java.net/18/)をダウンロードしてインストールします(バージョンはJDK 9より新しい必要があります)。 -2. VS Code拡張機能マーケットプレイスで`java`を検索し、Extension Pack for Javaをインストールします。 +1. [OpenJDK](https://jdk.java.net/18/) をダウンロードしてインストールします(バージョンは JDK 9 より新しい必要があります)。 +2. VS Code の拡張機能マーケットプレイスで `java` を検索し、Extension Pack for Java をインストールします。 -### C#環境 +### C# 環境 -1. [.Net 8.0](https://dotnet.microsoft.com/en-us/download)をダウンロードしてインストールします。 -2. VS Code拡張機能マーケットプレイスで`C# Dev Kit`を検索し、C# Dev Kitをインストールします([設定チュートリアル](https://code.visualstudio.com/docs/csharp/get-started))。 -3. Visual Studioを使用することもできます([インストールチュートリアル](https://learn.microsoft.com/zh-cn/visualstudio/install/install-visual-studio?view=vs-2022))。 +1. [.Net 8.0](https://dotnet.microsoft.com/en-us/download) をダウンロードしてインストールします。 +2. VS Code の拡張機能マーケットプレイスで `C# Dev Kit` を検索し、C# Dev Kit をインストールします([設定チュートリアル](https://code.visualstudio.com/docs/csharp/get-started))。 +3. Visual Studio を使用することもできます([インストール手順](https://learn.microsoft.com/zh-cn/visualstudio/install/install-visual-studio?view=vs-2022))。 -### Go環境 +### Go 環境 -1. [go](https://go.dev/dl/)をダウンロードしてインストールします。 -2. VS Code拡張機能マーケットプレイスで`go`を検索し、Goをインストールします。 -3. `Ctrl + Shift + P`を押してコマンドバーを呼び出し、goと入力し、`Go: Install/Update Tools`を選択し、すべてを選択してインストールします。 +1. [go](https://go.dev/dl/) をダウンロードしてインストールします。 +2. VS Code の拡張機能マーケットプレイスで `go` を検索し、Go をインストールします。 +3. ショートカットキー `Ctrl + Shift + P` を押してコマンドパレットを開き、go と入力して `Go: Install/Update Tools` を選択し、すべてにチェックを入れてインストールします。 -### Swift環境 +### Swift 環境 -1. [Swift](https://www.swift.org/download/)をダウンロードしてインストールします。 -2. VS Code拡張機能マーケットプレイスで`swift`を検索し、[Swift for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang)をインストールします。 +1. [Swift](https://www.swift.org/download/) をダウンロードしてインストールします。 +2. VS Code の拡張機能マーケットプレイスで `swift` を検索し、[Swift for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang) をインストールします。 -### JavaScript環境 +### JavaScript 環境 -1. [Node.js](https://nodejs.org/en/)をダウンロードしてインストールします。 -2. (オプション)VS Code拡張機能マーケットプレイスで`Prettier`を検索し、コードフォーマッティングツールをインストールします。 +1. [Node.js](https://nodejs.org/en/) をダウンロードしてインストールします。 +2. (任意)VS Code の拡張機能マーケットプレイスで `Prettier` を検索し、コード整形ツールをインストールします。 -### TypeScript環境 +### TypeScript 環境 -1. JavaScript環境と同じインストール手順に従います。 -2. [TypeScript Execute (tsx)](https://github.com/privatenumber/tsx?tab=readme-ov-file#global-installation)をインストールします。 -3. VS Code拡張機能マーケットプレイスで`typescript`を検索し、[Pretty TypeScript Errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors)をインストールします。 +1. JavaScript 環境と同じ手順でインストールします。 +2. [TypeScript Execute (tsx)](https://github.com/privatenumber/tsx?tab=readme-ov-file#global-installation) をインストールします。 +3. VS Code の拡張機能マーケットプレイスで `typescript` を検索し、[Pretty TypeScript Errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors) をインストールします。 -### Dart環境 +### Dart 環境 -1. [Dart](https://dart.dev/get-dart)をダウンロードしてインストールします。 -2. VS Code拡張機能マーケットプレイスで`dart`を検索し、[Dart](https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code)をインストールします。 +1. [Dart](https://dart.dev/get-dart) をダウンロードしてインストールします。 +2. VS Code の拡張機能マーケットプレイスで `dart` を検索し、[Dart](https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code) をインストールします。 -### Rust環境 +### Rust 環境 -1. [Rust](https://www.rust-lang.org/tools/install)をダウンロードしてインストールします。 -2. VS Code拡張機能マーケットプレイスで`rust`を検索し、[rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)をインストールします。 +1. [Rust](https://www.rust-lang.org/tools/install) をダウンロードしてインストールします。 +2. VS Code の拡張機能マーケットプレイスで `rust` を検索し、[rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) をインストールします。 diff --git a/ja/docs/chapter_appendix/terminology.md b/ja/docs/chapter_appendix/terminology.md index 779fc1085..e967b2540 100644 --- a/ja/docs/chapter_appendix/terminology.md +++ b/ja/docs/chapter_appendix/terminology.md @@ -1,137 +1,137 @@ # 用語集 -下の表は本書に登場する重要な用語をリストアップしており、以下の点に注意する価値があります。 +以下の表は、本書に登場する重要な用語を一覧にしたものです。特に次の点に注意してください。 -- 英語文献を読みやすくするため、用語の英語名を覚えることをお勧めします。 -- 一部の用語は簡体字中国語と繁体字中国語で異なる名前を持ちます。 +- 名詞の英語表現も覚えておくと、英語文献を読む際に役立ちます。 +- 一部の名詞は、簡体字中国語と繁体字中国語で呼び方が異なります。

  データ構造とアルゴリズムの重要用語

-| English | 日本語 | 简体中文 | 繁体中文 | -| ------------------------------ | ---------------------- | -------------- | -------------- | -| algorithm | アルゴリズム | 算法 | 演算法 | -| data structure | データ構造 | 数据结构 | 資料結構 | -| code | コード | 代码 | 程式碼 | -| file | ファイル | 文件 | 檔案 | -| function | 関数 | 函数 | 函式 | -| method | メソッド | 方法 | 方法 | -| variable | 変数 | 变量 | 變數 | -| asymptotic complexity analysis | 漸近計算量解析 | 渐近复杂度分析 | 漸近複雜度分析 | -| time complexity | 時間計算量 | 时间复杂度 | 時間複雜度 | -| space complexity | 空間計算量 | 空间复杂度 | 空間複雜度 | -| loop | ループ | 循环 | 迴圈 | -| iteration | 反復 | 迭代 | 迭代 | -| recursion | 再帰 | 递归 | 遞迴 | -| tail recursion | 末尾再帰 | 尾递归 | 尾遞迴 | -| recursion tree | 再帰木 | 递归树 | 遞迴樹 | -| big-$O$ notation | ビッグO記法 | 大 $O$ 记号 | 大 $O$ 記號 | -| asymptotic upper bound | 漸近上界 | 渐近上界 | 漸近上界 | -| sign-magnitude | 符号と絶対値 | 原码 | 原碼 | -| 1's complement | 1の補数 | 反码 | 一補數 | -| 2's complement | 2の補数 | 补码 | 二補數 | -| array | 配列 | 数组 | 陣列 | -| index | インデックス | 索引 | 索引 | -| linked list | 連結リスト | 链表 | 鏈結串列 | -| linked list node, list node | 連結リストノード | 链表节点 | 鏈結串列節點 | -| head node | 先頭ノード | 头节点 | 頭節點 | -| tail node | 末尾ノード | 尾节点 | 尾節點 | -| list | リスト | 列表 | 串列 | -| dynamic array | 動的配列 | 动态数组 | 動態陣列 | -| hard disk | ハードディスク | 硬盘 | 硬碟 | -| random-access memory (RAM) | メモリ | 内存 | 記憶體 | -| cache memory | キャッシュメモリ | 缓存 | 快取 | -| cache miss | キャッシュミス | 缓存未命中 | 快取未命中 | -| cache hit rate | キャッシュヒット率 | 缓存命中率 | 快取命中率 | -| stack | スタック | 栈 | 堆疊 | -| top of the stack | スタックトップ | 栈顶 | 堆疊頂 | -| bottom of the stack | スタックボトム | 栈底 | 堆疊底 | -| queue | キュー | 队列 | 佇列 | -| double-ended queue | 両端キュー | 双向队列 | 雙向佇列 | -| front of the queue | キューの先頭 | 队首 | 佇列首 | -| rear of the queue | キューの末尾 | 队尾 | 佇列尾 | -| hash table | ハッシュテーブル | 哈希表 | 雜湊表 | -| hash set | ハッシュセット | 哈希集合 | 雜湊集合 | -| bucket | バケット | 桶 | 桶 | -| hash function | ハッシュ関数 | 哈希函数 | 雜湊函式 | -| hash collision | ハッシュ衝突 | 哈希冲突 | 雜湊衝突 | -| load factor | 負荷率 | 负载因子 | 負載因子 | -| separate chaining | チェイン法 | 链式地址 | 鏈結位址 | -| open addressing | オープンアドレス法 | 开放寻址 | 開放定址 | -| linear probing | 線形プローブ法 | 线性探测 | 線性探查 | -| lazy deletion | 遅延削除 | 懒删除 | 懶刪除 | -| binary tree | 二分木 | 二叉树 | 二元樹 | -| tree node | 木のノード | 树节点 | 樹節點 | -| left-child node | 左の子ノード | 左子节点 | 左子節點 | -| right-child node | 右の子ノード | 右子节点 | 右子節點 | -| parent node | 親ノード | 父节点 | 父節點 | -| left subtree | 左の部分木 | 左子树 | 左子樹 | -| right subtree | 右の部分木 | 右子树 | 右子樹 | -| root node | ルートノード | 根节点 | 根節點 | -| leaf node | 葉ノード | 叶节点 | 葉節點 | -| edge | エッジ | 边 | 邊 | -| level | レベル | 层 | 層 | -| degree | 次数 | 度 | 度 | -| height | 高さ | 高度 | 高度 | -| depth | 深さ | 深度 | 深度 | -| perfect binary tree | 完全二分木 | 完美二叉树 | 完美二元樹 | -| complete binary tree | 完全二分木 | 完全二叉树 | 完全二元樹 | -| full binary tree | 満二分木 | 完满二叉树 | 完滿二元樹 | -| balanced binary tree | 平衡二分木 | 平衡二叉树 | 平衡二元樹 | -| binary search tree | 二分探索木 | 二叉搜索树 | 二元搜尋樹 | -| AVL tree | AVL木 | AVL 树 | AVL 樹 | -| red-black tree | 赤黒木 | 红黑树 | 紅黑樹 | -| level-order traversal | レベル順走査 | 层序遍历 | 層序走訪 | -| breadth-first traversal | 幅優先走査 | 广度优先遍历 | 廣度優先走訪 | -| depth-first traversal | 深さ優先走査 | 深度优先遍历 | 深度優先走訪 | -| binary search tree | 二分探索木 | 二叉搜索树 | 二元搜尋樹 | -| balanced binary search tree | 平衡二分探索木 | 平衡二叉搜索树 | 平衡二元搜尋樹 | -| balance factor | 平衡因子 | 平衡因子 | 平衡因子 | -| heap | ヒープ | 堆 | 堆積 | -| max heap | 最大ヒープ | 大顶堆 | 大頂堆積 | -| min heap | 最小ヒープ | 小顶堆 | 小頂堆積 | -| priority queue | 優先度キュー | 优先队列 | 優先佇列 | -| heapify | ヒープ化 | 堆化 | 堆積化 | -| top-$k$ problem | Top-$k$ 問題 | Top-$k$ 问题 | Top-$k$ 問題 | -| graph | グラフ | 图 | 圖 | -| vertex | 頂点 | 顶点 | 頂點 | -| undirected graph | 無向グラフ | 无向图 | 無向圖 | -| directed graph | 有向グラフ | 有向图 | 有向圖 | -| connected graph | 連結グラフ | 连通图 | 連通圖 | -| disconnected graph | 非連結グラフ | 非连通图 | 非連通圖 | -| weighted graph | 重み付きグラフ | 有权图 | 有權圖 | -| adjacency | 隣接 | 邻接 | 鄰接 | -| path | パス | 路径 | 路徑 | -| in-degree | 入次数 | 入度 | 入度 | -| out-degree | 出次数 | 出度 | 出度 | -| adjacency matrix | 隣接行列 | 邻接矩阵 | 鄰接矩陣 | -| adjacency list | 隣接リスト | 邻接表 | 鄰接表 | -| breadth-first search | 幅優先探索 | 广度优先搜索 | 廣度優先搜尋 | -| depth-first search | 深さ優先探索 | 深度优先搜索 | 深度優先搜尋 | -| binary search | 二分探索 | 二分查找 | 二分搜尋 | -| searching algorithm | 探索アルゴリズム | 搜索算法 | 搜尋演算法 | -| sorting algorithm | ソートアルゴリズム | 排序算法 | 排序演算法 | -| selection sort | 選択ソート | 选择排序 | 選擇排序 | -| bubble sort | バブルソート | 冒泡排序 | 泡沫排序 | -| insertion sort | 挿入ソート | 插入排序 | 插入排序 | -| quick sort | クイックソート | 快速排序 | 快速排序 | -| merge sort | マージソート | 归并排序 | 合併排序 | -| heap sort | ヒープソート | 堆排序 | 堆積排序 | -| bucket sort | バケットソート | 桶排序 | 桶排序 | -| counting sort | 計数ソート | 计数排序 | 計數排序 | -| radix sort | 基数ソート | 基数排序 | 基數排序 | -| divide and conquer | 分割統治法 | 分治 | 分治 | -| hanota problem | ハノイの塔問題 | 汉诺塔问题 | 河內塔問題 | -| backtracking algorithm | バックトラッキング | 回溯算法 | 回溯演算法 | -| constraint | 制約 | 约束 | 約束 | -| solution | 解 | 解 | 解 | -| state | 状態 | 状态 | 狀態 | -| pruning | 枝刈り | 剪枝 | 剪枝 | -| permutations problem | 順列問題 | 全排列问题 | 全排列問題 | -| subset-sum problem | 部分集合和問題 | 子集和问题 | 子集合問題 | -| $n$-queens problem | $n$ クイーン問題 | $n$ 皇后问题 | $n$ 皇后問題 | -| dynamic programming | 動的プログラミング | 动态规划 | 動態規劃 | -| initial state | 初期状態 | 初始状态 | 初始狀態 | -| state-transition equation | 状態遷移方程式 | 状态转移方程 | 狀態轉移方程 | -| knapsack problem | ナップサック問題 | 背包问题 | 背包問題 | -| edit distance problem | 編集距離問題 | 编辑距离问题 | 編輯距離問題 | -| greedy algorithm | 貪欲アルゴリズム | 贪心算法 | 貪婪演算法 | +| English | 日本語 | 日本語 | +| ------------------------------ | -------------- | -------------- | +| algorithm | アルゴリズム | アルゴリズム | +| data structure | データ構造 | データ構造 | +| code | コード | コード | +| file | ファイル | ファイル | +| function | 関数 | 関数 | +| method | メソッド | メソッド | +| variable | 変数 | 変数 | +| asymptotic complexity analysis | 漸近計算量解析 | 漸近計算量解析 | +| time complexity | 時間計算量 | 時間計算量 | +| space complexity | 空間計算量 | 空間計算量 | +| loop | ループ | ループ | +| iteration | 反復 | 反復 | +| recursion | 再帰 | 再帰 | +| tail recursion | 末尾再帰 | 末尾再帰 | +| recursion tree | 再帰木 | 再帰木 | +| big-$O$ notation | ビッグオー記法 | ビッグオー記法 | +| asymptotic upper bound | 漸近上界 | 漸近上界 | +| sign-magnitude | 符号絶対値表現 | 符号絶対値表現 | +| 1’s complement | 1の補数 | 1の補数 | +| 2’s complement | 2の補数 | 2の補数 | +| array | 配列 | 配列 | +| index | インデックス | インデックス | +| linked list | 連結リスト | 連結リスト | +| linked list node, list node | 連結リストノード | 連結リストノード | +| head node | 先頭ノード | 先頭ノード | +| tail node | 末尾ノード | 末尾ノード | +| list | リスト | リスト | +| dynamic array | 動的配列 | 動的配列 | +| hard disk | ハードディスク | ハードディスク | +| random-access memory (RAM) | メモリ | メモリ | +| cache memory | キャッシュ | キャッシュ | +| cache miss | キャッシュミス | キャッシュミス | +| cache hit rate | キャッシュヒット率 | キャッシュヒット率 | +| stack | スタック | スタック | +| top of the stack | スタックトップ | スタックトップ | +| bottom of the stack | スタックボトム | スタックボトム | +| queue | キュー | キュー | +| double-ended queue | 両端キュー | 両端キュー | +| front of the queue | キュー先頭 | キュー先頭 | +| rear of the queue | キュー末尾 | キュー末尾 | +| hash table | ハッシュテーブル | ハッシュテーブル | +| hash set | ハッシュ集合 | ハッシュ集合 | +| bucket | バケット | バケット | +| hash function | ハッシュ関数 | ハッシュ関数 | +| hash collision | ハッシュ衝突 | ハッシュ衝突 | +| load factor | 負荷率 | 負荷率 | +| separate chaining | 連鎖アドレス法 | 連鎖アドレス法 | +| open addressing | オープンアドレス法 | オープンアドレス法 | +| linear probing | 線形探索 | 線形探索 | +| lazy deletion | 遅延削除 | 遅延削除 | +| binary tree | 二分木 | 二分木 | +| tree node | ノード | ノード | +| left-child node | 左子ノード | 左子ノード | +| right-child node | 右子ノード | 右子ノード | +| parent node | 親ノード | 親ノード | +| left subtree | 左部分木 | 左部分木 | +| right subtree | 右部分木 | 右部分木 | +| root node | 根ノード | 根ノード | +| leaf node | 葉ノード | 葉ノード | +| edge | 辺 | 辺 | +| level | レベル | レベル | +| degree | 次数 | 次数 | +| height | 高さ | 高さ | +| depth | 深さ | 深さ | +| perfect binary tree | 完備二分木 | 完備二分木 | +| complete binary tree | 完全二分木 | 完全二分木 | +| full binary tree | 満二分木 | 満二分木 | +| balanced binary tree | 平衡二分木 | 平衡二分木 | +| binary search tree | 二分探索木 | 二分探索木 | +| AVL tree | AVL 木 | AVL 木 | +| red-black tree | 赤黒木 | 赤黒木 | +| level-order traversal | レベル順走査 | レベル順走査 | +| breadth-first traversal | 幅優先走査 | 幅優先走査 | +| depth-first traversal | 深さ優先走査 | 深さ優先走査 | +| binary search tree | 二分探索木 | 二分探索木 | +| balanced binary search tree | 平衡二分探索木 | 平衡二分探索木 | +| balance factor | 平衡係数 | 平衡係数 | +| heap | ヒープ | ヒープ | +| max heap | 最大ヒープ | 最大ヒープ | +| min heap | 最小ヒープ | 最小ヒープ | +| priority queue | 優先度付きキュー | 優先度付きキュー | +| heapify | ヒープ化 | ヒープ化 | +| top-$k$ problem | Top-$k$ 問題 | Top-$k$ 問題 | +| graph | グラフ | グラフ | +| vertex | 頂点 | 頂点 | +| undirected graph | 無向グラフ | 無向グラフ | +| directed graph | 有向グラフ | 有向グラフ | +| connected graph | 連結グラフ | 連結グラフ | +| disconnected graph | 非連結グラフ | 非連結グラフ | +| weighted graph | 重み付きグラフ | 重み付きグラフ | +| adjacency | 隣接 | 隣接 | +| path | 経路 | 経路 | +| in-degree | 入次数 | 入次数 | +| out-degree | 出次数 | 出次数 | +| adjacency matrix | 隣接行列 | 隣接行列 | +| adjacency list | 隣接リスト | 隣接リスト | +| breadth-first search | 幅優先探索 | 幅優先探索 | +| depth-first search | 深さ優先探索 | 深さ優先探索 | +| binary search | 二分探索 | 二分探索 | +| searching algorithm | 探索アルゴリズム | 探索アルゴリズム | +| sorting algorithm | ソートアルゴリズム | ソートアルゴリズム | +| selection sort | 選択ソート | 選択ソート | +| bubble sort | バブルソート | バブルソート | +| insertion sort | 挿入ソート | 挿入ソート | +| quick sort | クイックソート | クイックソート | +| merge sort | マージソート | マージソート | +| heap sort | ヒープソート | ヒープソート | +| bucket sort | バケットソート | バケットソート | +| counting sort | 計数ソート | 計数ソート | +| radix sort | 基数ソート | 基数ソート | +| divide and conquer | 分割統治 | 分割統治 | +| hanota problem | ハノイの塔問題 | ハノイの塔問題 | +| backtracking algorithm | バックトラッキングアルゴリズム | バックトラッキングアルゴリズム | +| constraint | 制約 | 制約 | +| solution | 解 | 解 | +| state | 状態 | 状態 | +| pruning | 枝刈り | 枝刈り | +| permutations problem | 全順列問題 | 全順列問題 | +| subset-sum problem | 部分和問題 | 部分和問題 | +| $n$-queens problem | $n$ クイーン問題 | $n$ クイーン問題 | +| dynamic programming | 動的計画法 | 動的計画法 | +| initial state | 初期状態 | 初期状態 | +| state-transition equation | 状態遷移方程式 | 状態遷移方程式 | +| knapsack problem | ナップサック問題 | ナップサック問題 | +| edit distance problem | 編集距離問題 | 編集距離問題 | +| greedy algorithm | 貪欲法 | 貪欲法 | diff --git a/ja/docs/chapter_array_and_linkedlist/array.md b/ja/docs/chapter_array_and_linkedlist/array.md index 8df90c20f..60842af8a 100644 --- a/ja/docs/chapter_array_and_linkedlist/array.md +++ b/ja/docs/chapter_array_and_linkedlist/array.md @@ -1,19 +1,19 @@ # 配列 -配列は線形データ構造で、同じような項目が並んでいるようなもので、コンピュータのメモリ内の連続した空間に一緒に格納されます。これは整理された格納を維持するシーケンスのようなものです。この並びの各項目には、インデックスとして知られる独自の「位置」があります。以下の図を参照して、配列の動作を観察し、これらの重要な用語を理解してください。 +配列(array)は線形データ構造の一種であり、同じ型の要素を連続したメモリ領域に格納します。要素が配列内にある位置を、その要素のインデックス(index)と呼びます。下図は、配列の主要な概念と格納方式を示しています。 -![配列の定義と格納方法](array.assets/array_definition.png) +![配列の定義と格納方式](array.assets/array_definition.png) ## 配列の一般的な操作 ### 配列の初期化 -配列は必要に応じて2つの方法で初期化できます:初期値なしまたは指定された初期値付きです。初期値が指定されていない場合、ほとんどのプログラミング言語は配列要素を$0$に設定します: +必要に応じて、配列の初期化方法として初期値なしと初期値ありの 2 種類を使い分けられます。初期値を指定しない場合、多くのプログラミング言語では配列要素は $0$ に初期化されます。 === "Python" ```python title="array.py" - # 配列を初期化 + # 配列を初期化する arr: list[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ] nums: list[int] = [1, 3, 2, 5, 4] ``` @@ -21,11 +21,11 @@ === "C++" ```cpp title="array.cpp" - /* 配列を初期化 */ - // スタックに格納 + /* 配列を初期化する */ + // スタック上に格納 int arr[5]; int nums[5] = { 1, 3, 2, 5, 4 }; - // ヒープに格納(手動でのメモリ解放が必要) + // ヒープ上に格納(手動で領域を解放する必要がある) int* arr1 = new int[5]; int* nums1 = new int[5] { 1, 3, 2, 5, 4 }; ``` @@ -33,7 +33,7 @@ === "Java" ```java title="array.java" - /* 配列を初期化 */ + /* 配列を初期化する */ int[] arr = new int[5]; // { 0, 0, 0, 0, 0 } int[] nums = { 1, 3, 2, 5, 4 }; ``` @@ -41,7 +41,7 @@ === "C#" ```csharp title="array.cs" - /* 配列を初期化 */ + /* 配列を初期化する */ int[] arr = new int[5]; // [ 0, 0, 0, 0, 0 ] int[] nums = [1, 3, 2, 5, 4]; ``` @@ -49,18 +49,18 @@ === "Go" ```go title="array.go" - /* 配列を初期化 */ + /* 配列を初期化する */ var arr [5]int - // Goでは、長さを指定([5]int)すると配列を示し、指定しない([]int)とスライスを示します。 - // Goの配列はコンパイル時に固定長を持つよう設計されているため、長さの指定には定数のみ使用できます。 - // extend()メソッドの実装の便宜上、ここではSliceを配列として扱います。 + // Go では、長さを指定する場合([5]int)は配列であり、長さを指定しない場合([]int)はスライス + // Go の配列はコンパイル時に長さが確定するよう設計されているため、長さの指定には定数しか使用できない + // 拡張 extend() メソッドを実装しやすくするため、以下ではスライス(Slice)を配列(Array)として扱う nums := []int{1, 3, 2, 5, 4} ``` === "Swift" ```swift title="array.swift" - /* 配列を初期化 */ + /* 配列を初期化する */ let arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0] let nums = [1, 3, 2, 5, 4] ``` @@ -68,7 +68,7 @@ === "JS" ```javascript title="array.js" - /* 配列を初期化 */ + /* 配列を初期化する */ var arr = new Array(5).fill(0); var nums = [1, 3, 2, 5, 4]; ``` @@ -76,7 +76,7 @@ === "TS" ```typescript title="array.ts" - /* 配列を初期化 */ + /* 配列を初期化する */ let arr: number[] = new Array(5).fill(0); let nums: number[] = [1, 3, 2, 5, 4]; ``` @@ -84,7 +84,7 @@ === "Dart" ```dart title="array.dart" - /* 配列を初期化 */ + /* 配列を初期化する */ List arr = List.filled(5, 0); // [0, 0, 0, 0, 0] List nums = [1, 3, 2, 5, 4]; ``` @@ -92,20 +92,20 @@ === "Rust" ```rust title="array.rs" - /* 配列を初期化 */ + /* 配列を初期化する */ let arr: [i32; 5] = [0; 5]; // [0, 0, 0, 0, 0] let slice: &[i32] = &[0; 5]; - // Rustでは、長さを指定([i32; 5])すると配列を示し、指定しない(&[i32])とスライスを示します。 - // Rustの配列はコンパイル時に固定長を持つよう設計されているため、長さの指定には定数のみ使用できます。 - // 一般的にRustでは動的配列としてVectorが使用されます。 - // extend()メソッドの実装の便宜上、ここではベクターを配列として扱います。 + // Rust では、長さを指定する場合([i32; 5])は配列であり、長さを指定しない場合(&[i32])はスライス + // Rust の配列はコンパイル時に長さが確定するよう設計されているため、長さの指定には定数しか使用できない + // Vector は Rust で一般に動的配列として使われる型 + // 拡張 extend() メソッドを実装しやすくするため、以下では vector を配列(array)として扱う let nums: Vec = vec![1, 3, 2, 5, 4]; ``` === "C" ```c title="array.c" - /* 配列を初期化 */ + /* 配列を初期化する */ int arr[5] = { 0 }; // { 0, 0, 0, 0, 0 } int nums[5] = { 1, 3, 2, 5, 4 }; ``` @@ -113,18 +113,32 @@ === "Kotlin" ```kotlin title="array.kt" - + /* 配列を初期化する */ + var arr = IntArray(5) // { 0, 0, 0, 0, 0 } + var nums = intArrayOf(1, 3, 2, 5, 4) ``` +=== "Ruby" + + ```ruby title="array.rb" + # 配列を初期化する + arr = Array.new(5, 0) + nums = [1, 3, 2, 5, 4] + ``` + +??? pythontutor "実行の可視化" + + https://pythontutor.com/render.html#code=%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0Aarr%20%3D%20%5B0%5D%20*%205%20%20%23%20%5B%200,%200,%200,%200,%200%20%5D%0Anums%20%3D%20%5B1,%203,%202,%205,%204%5D&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + ### 要素へのアクセス -配列内の要素は連続したメモリ空間に格納されるため、各要素のメモリアドレスを計算することが簡単になります。以下の図に示されている公式は、配列のメモリアドレス(特に、最初の要素のアドレス)と要素のインデックスを利用して、要素のメモリアドレスを決定するのに役立ちます。この計算により、目的の要素への直接アクセスが合理化されます。 +配列要素は連続したメモリ領域に格納されるため、要素のメモリアドレスの計算は非常に容易です。配列のメモリアドレス(先頭要素のメモリアドレス)とある要素のインデックスが与えられれば、下図の式を使ってその要素のメモリアドレスを計算でき、直接その要素にアクセスできます。 -![配列要素のメモリアドレス計算](array.assets/array_memory_location_calculation.png) +![配列要素のメモリアドレスの計算](array.assets/array_memory_location_calculation.png) -上の図で観察されるように、配列のインデックスは慣例的に$0$から始まります。これは直感に反するように見えるかもしれません。数を数えるのは通常$1$から始まるためですが、アドレス計算公式内では、**インデックスは本質的にメモリアドレスからのオフセット**です。最初の要素のアドレスでは、このオフセットは$0$で、そのインデックスが$0$であることを検証しています。 +上図を見ると、配列の最初の要素のインデックスは $0$ であり、これは少し直感に反するように思えます。というのも、$1$ から数え始めるほうが自然だからです。しかし、アドレス計算式の観点では、**インデックスの本質はメモリアドレスのオフセット**です。先頭要素のアドレスのオフセットは $0$ であるため、そのインデックスが $0$ なのは妥当です。 -配列内の要素へのアクセスは非常に効率的で、$O(1)$時間で任意の要素にランダムアクセスできます。 +配列では要素へのアクセスは非常に効率的であり、$O(1)$ 時間で任意の要素にランダムアクセスできます。 ```src [file]{array}-[class]{}-[func]{random_access} @@ -132,11 +146,11 @@ ### 要素の挿入 -配列要素はメモリ内で密に詰まっており、それらの間に追加データを収容するための空間はありません。以下の図に示すように、配列の中央に要素を挿入するには、後続のすべての要素を1つずつ後ろにシフトして、新しい要素のための空間を作る必要があります。 +配列要素はメモリ内で「ぴったり隣接して」おり、その間にほかのデータを格納する余地はありません。下図のように、配列の途中に要素を挿入したい場合は、その要素より後ろにあるすべての要素を 1 つずつ後ろへずらし、その後でそのインデックスに要素を代入する必要があります。 -![配列要素挿入の例](array.assets/array_insert_element.png) +![配列への要素挿入の例](array.assets/array_insert_element.png) -配列の長さが固定されているため、要素を挿入すると必然的に配列の最後の要素が失われることに注意することが重要です。この問題を解決する方法は「リスト」の章で探求されます。 +注意すべき点として、配列の長さは固定であるため、要素を 1 つ挿入すると配列末尾の要素が必ず「失われ」ます。この問題の解決策は「リスト」の章で扱います。 ```src [file]{array}-[class]{}-[func]{insert} @@ -144,25 +158,25 @@ ### 要素の削除 -同様に、以下の図に示すように、インデックス$i$の要素を削除するには、インデックス$i$に続くすべての要素を1つずつ前に移動する必要があります。 +同様に、下図のように、インデックス $i$ の要素を削除したい場合は、インデックス $i$ より後ろの要素をすべて 1 つずつ前へずらす必要があります。 -![配列要素削除の例](array.assets/array_remove_element.png) +![配列からの要素削除の例](array.assets/array_remove_element.png) -削除後、元の最後の要素は「意味がない」ものになるため、特定の修正は必要ないことに注意してください。 +注意してください。要素の削除が完了すると、もともとの末尾要素は「意味を持たない」状態になるため、わざわざ変更する必要はありません。 ```src [file]{array}-[class]{}-[func]{remove} ``` -要約すると、配列の挿入と削除操作には以下の欠点があります: +全体として見ると、配列の挿入と削除には次の欠点があります。 -- **高い時間計算量**:配列の挿入と削除の両方の平均時間計算量は$O(n)$で、ここで$n$は配列の長さです。 -- **要素の損失**:配列の長さが固定されているため、挿入時に配列の容量を超える要素は失われます。 -- **メモリの無駄**:より長い配列を初期化して前部分のみを利用すると、挿入時に「意味のない」末尾要素が生じ、メモリ空間の無駄につながります。 +- **時間計算量が高い**:配列の挿入と削除の平均時間計算量はいずれも $O(n)$ であり、ここで $n$ は配列長です。 +- **要素が失われる**:配列の長さは不変であるため、要素を挿入すると配列長の範囲を超えた要素は失われます。 +- **メモリの浪費**:やや長めの配列を初期化して先頭部分だけを使うこともでき、この場合データ挿入時に失われる末尾要素はすべて「無意味」ですが、その代わり一部のメモリ領域が無駄になります。 ### 配列の走査 -ほとんどのプログラミング言語では、インデックスを使用するか、各要素を直接反復することで配列を走査できます: +ほとんどのプログラミング言語では、インデックスを使って配列を走査することも、各要素を直接取り出しながら走査することもできます。 ```src [file]{array}-[class]{}-[func]{traverse} @@ -170,9 +184,9 @@ ### 要素の検索 -配列内の特定の要素を見つけることは、配列を反復し、各要素をチェックして目的の値と一致するかどうかを決定することを含みます。 +配列内で指定した要素を探すには、配列を走査し、各反復で要素値が一致するかを判定し、一致したら対応するインデックスを出力します。 -配列は線形データ構造であるため、この操作は一般的に「線形探索」と呼ばれます。 +配列は線形データ構造であるため、上記の検索操作は「線形探索」と呼ばれます。 ```src [file]{array}-[class]{}-[func]{find} @@ -180,34 +194,34 @@ ### 配列の拡張 -複雑なシステム環境では、安全な容量拡張のために配列の後にメモリ空間の可用性を確保することが困難になります。その結果、ほとんどのプログラミング言語では、**配列の長さは不変**です。 +複雑なシステム環境では、配列の後方にあるメモリ領域が利用可能であることをプログラム側で保証できず、そのため安全に配列容量を拡張できません。したがって、ほとんどのプログラミング言語では、**配列の長さは不変です**。 -配列を拡張するには、より大きな配列を作成し、元の配列から要素をコピーする必要があります。この操作の時間計算量は$O(n)$で、大きな配列では時間がかかる可能性があります。コードは以下の通りです: +配列を拡張したい場合は、より大きな新しい配列を作り、元の配列の要素を順に新配列へコピーする必要があります。これは $O(n)$ の操作であり、配列が大きい場合は非常に時間がかかります。コードは次のとおりです。 ```src [file]{array}-[class]{}-[func]{extend} ``` -## 配列の利点と制限 +## 配列の利点と限界 -配列は連続したメモリ空間に格納され、同じ型の要素で構成されます。このアプローチは、システムがデータ構造操作の効率を最適化するために活用できる実質的な事前情報を提供します。 +配列は連続したメモリ領域に格納され、要素の型も同一です。この方法には豊富な事前情報が含まれており、システムはそれらを利用してデータ構造の操作効率を最適化できます。 -- **高い空間効率**:配列はデータのための連続したメモリブロックを割り当て、追加の構造的オーバーヘッドの必要性を排除します。 -- **ランダムアクセスのサポート**:配列は任意の要素への$O(1)$時間アクセスを可能にします。 -- **キャッシュ局所性**:配列要素にアクセスするとき、コンピュータはそれらを読み込むだけでなく、周囲のデータもキャッシュし、高速キャッシュを利用して後続の操作速度を向上させます。 +- **空間効率が高い**:配列はデータに連続したメモリブロックを割り当てるため、追加の構造オーバーヘッドが不要です。 +- **ランダムアクセスをサポートする**:配列では任意の要素に $O(1)$ 時間でアクセスできます。 +- **キャッシュ局所性**:配列要素にアクセスする際、コンピュータはその要素だけでなく周囲のデータもキャッシュするため、高速キャッシュを利用して後続操作の実行速度を高められます。 -しかし、連続空間格納は諸刃の剣で、以下の制限があります: +連続領域への格納は諸刃の剣であり、次のような制約があります。 -- **挿入と削除の効率が低い**:配列に多くの要素が蓄積されると、要素の挿入や削除には大量の要素をシフトする必要があります。 -- **固定長**:配列の長さは初期化後に固定されます。配列を拡張するには、すべてのデータを新しい配列にコピーする必要があり、大きなコストがかかります。 -- **空間の無駄**:割り当てられた配列サイズが必要以上に大きい場合、余分な空間が無駄になります。 +- **挿入と削除の効率が低い**:配列内の要素が多い場合、挿入や削除では大量の要素を移動する必要があります。 +- **長さが不変**:配列は初期化後に長さが固定され、拡張するにはすべてのデータを新しい配列へコピーする必要があり、コストが大きくなります。 +- **空間の浪費**:配列に割り当てたサイズが実際の必要量を上回る場合、余分な領域は無駄になります。 ## 配列の典型的な応用 -配列は基本的で広く使用されるデータ構造です。様々なアルゴリズムで頻繁に応用され、複雑なデータ構造の実装に役立ちます。 +配列は基礎的で一般的なデータ構造であり、さまざまなアルゴリズムで頻繁に使われるだけでなく、多様な複雑データ構造の実装にも利用できます。 -- **ランダムアクセス**:配列はランダムサンプリングが必要なときのデータ格納に理想的です。インデックスに基づいてランダムシーケンスを生成することで、効率的にランダムサンプリングを実現できます。 -- **ソートと検索**:配列はソートと検索アルゴリズムで最も一般的に使用されるデータ構造です。クイックソート、マージソート、二分探索などの技術は主に配列で動作します。 -- **ルックアップテーブル**:配列は迅速な要素や関係の取得のための効率的なルックアップテーブルとして機能します。例えば、文字をASCIIコードにマッピングすることは、ASCIIコード値をインデックスとして使用し、対応する要素を配列に格納することで簡単になります。 -- **機械学習**:ニューラルネットワークの領域では、配列はベクトル、行列、テンソルを含む重要な線形代数演算の実行において重要な役割を果たします。配列はニューラルネットワークプログラミングにおいて主要かつ最も広範囲に使用されるデータ構造として機能します。 -- **データ構造の実装**:配列は、スタック、キュー、ハッシュ表、ヒープ、グラフなど、様々なデータ構造を実装するための構成要素として機能します。例えば、グラフの隣接行列表現は本質的に二次元配列です。 +- **ランダムアクセス**:いくつかのサンプルをランダムに抽出したい場合、配列に格納してランダムな系列を生成し、インデックスに基づいてランダムサンプリングを行えます。 +- **ソートと探索**:配列はソートアルゴリズムと探索アルゴリズムで最もよく使われるデータ構造です。クイックソート、マージソート、二分探索などは主に配列上で行われます。 +- **ルックアップテーブル**:ある要素やその対応関係を高速に調べる必要がある場合、配列をルックアップテーブルとして使えます。たとえば文字から ASCII コードへの対応を実装したいなら、文字の ASCII コード値をインデックスとし、対応する要素を配列の対応位置に格納できます。 +- **機械学習**:ニューラルネットワークでは、ベクトル、行列、テンソル間の線形代数演算が大量に使われ、これらのデータはいずれも配列の形で構築されます。配列はニューラルネットワークプログラミングで最もよく使われるデータ構造です。 +- **データ構造の実装**:配列はスタック、キュー、ハッシュテーブル、ヒープ、グラフなどのデータ構造の実装に利用できます。たとえば、グラフの隣接行列表現は実際には 2 次元配列です。 diff --git a/ja/docs/chapter_array_and_linkedlist/index.md b/ja/docs/chapter_array_and_linkedlist/index.md index 8a65046bd..943bfcf71 100644 --- a/ja/docs/chapter_array_and_linkedlist/index.md +++ b/ja/docs/chapter_array_and_linkedlist/index.md @@ -4,6 +4,6 @@ !!! abstract - データ構造の世界は頑丈なレンガの壁に似ています。 + データ構造の世界は、まるで重厚なれんがの壁のようです。 - 配列では、レンガがぴったりと整列し、それぞれが次のものと継ぎ目なく隣り合って、統一された形成を作っている姿を想像してください。一方、連結リストでは、これらのレンガが自由に散らばり、それらの間を優雅に編み込む蔦に抱かれています。 + 配列のれんがは整然と並び、一つひとつがぴったりと接しています。連結リストのれんがはあちこちに分散し、それらをつなぐつるがれんがのすき間を自由に行き交います。 diff --git a/ja/docs/chapter_array_and_linkedlist/linked_list.md b/ja/docs/chapter_array_and_linkedlist/linked_list.md index 37e4c53c1..f3e0af743 100644 --- a/ja/docs/chapter_array_and_linkedlist/linked_list.md +++ b/ja/docs/chapter_array_and_linkedlist/linked_list.md @@ -1,20 +1,20 @@ # 連結リスト -メモリ空間は、すべてのプログラム間で共有されるリソースです。複雑なシステム環境では、使用可能なメモリがメモリ空間全体に分散している可能性があります。配列に割り当てられるメモリは連続している必要があることを理解していますが、非常に大きな配列の場合、十分な大きさの連続メモリ空間を見つけるのは困難な場合があります。ここで、連結リストの柔軟な利点が明らかになります。 +メモリ空間はすべてのプログラムに共通の資源であり、複雑なシステム実行環境では、空きメモリがメモリの各所に散在している可能性があります。配列を格納するメモリ空間は連続していなければなりませんが、配列が非常に大きい場合、メモリはそのような大きな連続領域を提供できないことがあります。このとき、連結リストの柔軟性という利点が現れます。 -連結リストは線形データ構造であり、各要素はノードオブジェクトで、ノードは「参照」を通じて相互接続されています。これらの参照は後続ノードのメモリアドレスを保持し、1つのノードから次のノードへのナビゲーションを可能にします。 +連結リスト(linked list)は線形データ構造の一種であり、各要素はノードオブジェクトです。各ノードは「参照」によって接続されます。参照には次のノードのメモリアドレスが記録されており、これによって現在のノードから次のノードへアクセスできます。 -連結リストの設計では、ノードを連続するメモリアドレスを必要とせずに、メモリ位置全体に分散配置することができます。 +連結リストの設計では、各ノードをメモリの各所に分散して格納でき、それらのメモリアドレスは連続している必要がありません。 -![連結リストの定義と格納方法](linked_list.assets/linkedlist_definition.png) +![連結リストの定義と格納方式](linked_list.assets/linkedlist_definition.png) -上図に示すように、連結リストの基本的な構成要素はノードオブジェクトです。各ノードは2つの主要なコンポーネントで構成されています:ノードの「値」と次のノードへの「参照」です。 +上図を見ると、連結リストの構成単位はノード(node)オブジェクトです。各ノードは 2 つのデータ、すなわちノードの「値」と次のノードを指す「参照」を含みます。 -- 連結リストの最初のノードは「ヘッドノード」、最後のノードは「テールノード」です。 -- テールノードは「null」を指し、Javaでは`null`、C++では`nullptr`、Pythonでは`None`として指定されます。 -- C、C++、Go、Rustなどのポインタをサポートする言語では、この「参照」は通常「ポインタ」として実装されます。 +- 連結リストの最初のノードを「先頭ノード」、最後のノードを「末尾ノード」と呼びます。 +- 末尾ノードが指す先は「空」であり、Java、C++、Python ではそれぞれ `null`、`nullptr`、`None` と表記します。 +- C、C++、Go、Rust などポインタをサポートする言語では、上記の「参照」は「ポインタ」に置き換えるべきです。 -以下のコードが示すように、連結リストの`ListNode`は値を保持するだけでなく、追加の参照(またはポインタ)も維持する必要があります。したがって、**連結リストは同じ量のデータを格納する場合、配列よりも多くのメモリ空間を占有します**。 +以下のコードが示すように、連結リストノード `ListNode` は値のほかに、追加で 1 つの参照(ポインタ)を保持する必要があります。そのため、同じデータ量であれば、**連結リストは配列より多くのメモリ空間を消費します**。 === "Python" @@ -22,7 +22,7 @@ class ListNode: """連結リストノードクラス""" def __init__(self, val: int): - self.val: int = val # ノード値 + self.val: int = val # ノードの値 self.next: ListNode | None = None # 次のノードへの参照 ``` @@ -31,7 +31,7 @@ ```cpp title="" /* 連結リストノード構造体 */ struct ListNode { - int val; // ノード値 + int val; // ノードの値 ListNode *next; // 次のノードへのポインタ ListNode(int x) : val(x), next(nullptr) {} // コンストラクタ }; @@ -42,7 +42,7 @@ ```java title="" /* 連結リストノードクラス */ class ListNode { - int val; // ノード値 + int val; // ノードの値 ListNode next; // 次のノードへの参照 ListNode(int x) { val = x; } // コンストラクタ } @@ -52,8 +52,8 @@ ```csharp title="" /* 連結リストノードクラス */ - class ListNode(int x) { // コンストラクタ - int val = x; // ノード値 + class ListNode(int x) { //コンストラクタ + int val = x; // ノードの値 ListNode? next; // 次のノードへの参照 } ``` @@ -63,11 +63,11 @@ ```go title="" /* 連結リストノード構造体 */ type ListNode struct { - Val int // ノード値 + Val int // ノードの値 Next *ListNode // 次のノードへのポインタ } - // NewListNode コンストラクタ、新しい連結リストを作成 + // NewListNode コンストラクタ。新しい連結リストを作成する func NewListNode(val int) *ListNode { return &ListNode{ Val: val, @@ -81,7 +81,7 @@ ```swift title="" /* 連結リストノードクラス */ class ListNode { - var val: Int // ノード値 + var val: Int // ノードの値 var next: ListNode? // 次のノードへの参照 init(x: Int) { // コンストラクタ @@ -96,7 +96,7 @@ /* 連結リストノードクラス */ class ListNode { constructor(val, next) { - this.val = (val === undefined ? 0 : val); // ノード値 + this.val = (val === undefined ? 0 : val); // ノードの値 this.next = (next === undefined ? null : next); // 次のノードへの参照 } } @@ -110,7 +110,7 @@ val: number; next: ListNode | null; constructor(val?: number, next?: ListNode | null) { - this.val = val === undefined ? 0 : val; // ノード値 + this.val = val === undefined ? 0 : val; // ノードの値 this.next = next === undefined ? null : next; // 次のノードへの参照 } } @@ -121,7 +121,7 @@ ```dart title="" /* 連結リストノードクラス */ class ListNode { - int val; // ノード値 + int val; // ノードの値 ListNode? next; // 次のノードへの参照 ListNode(this.val, [this.next]); // コンストラクタ } @@ -135,7 +135,7 @@ /* 連結リストノードクラス */ #[derive(Debug)] struct ListNode { - val: i32, // ノード値 + val: i32, // ノードの値 next: Option>>, // 次のノードへのポインタ } ``` @@ -145,7 +145,7 @@ ```c title="" /* 連結リストノード構造体 */ typedef struct ListNode { - int val; // ノード値 + int val; // ノードの値 struct ListNode *next; // 次のノードへのポインタ } ListNode; @@ -162,19 +162,39 @@ === "Kotlin" ```kotlin title="" - + /* 連結リストノードクラス */ + // コンストラクタ + class ListNode(x: Int) { + val _val: Int = x // ノードの値 + val next: ListNode? = null // 次のノードへの参照 + } ``` -## 連結リストの一般的な操作 +=== "Ruby" + + ```ruby title="" + # 連結リストノードクラス + class ListNode + attr_accessor :val # ノードの値 + attr_accessor :next # 次のノードへの参照 + + def initialize(val=0, next_node=nil) + @val = val + @next = next_node + end + end + ``` + +## 連結リストの基本操作 ### 連結リストの初期化 -連結リストの構築は2段階のプロセスです:まず各ノードオブジェクトを初期化し、次にノード間の参照リンクを形成します。初期化後、ヘッドノードから`next`参照をたどってすべてのノードを順次巡回できます。 +連結リストの構築は 2 つの手順に分かれます。第 1 に各ノードオブジェクトを初期化し、第 2 にノード間の参照関係を構築します。初期化が完了したら、連結リストの先頭ノードから出発し、参照で `next` をたどってすべてのノードに順にアクセスできます。 === "Python" ```python title="linked_list.py" - # 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 + # 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 # 各ノードを初期化 n0 = ListNode(1) n1 = ListNode(3) @@ -191,7 +211,7 @@ === "C++" ```cpp title="linked_list.cpp" - /* 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 */ + /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */ // 各ノードを初期化 ListNode* n0 = new ListNode(1); ListNode* n1 = new ListNode(3); @@ -208,7 +228,7 @@ === "Java" ```java title="linked_list.java" - /* 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 */ + /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */ // 各ノードを初期化 ListNode n0 = new ListNode(1); ListNode n1 = new ListNode(3); @@ -225,7 +245,7 @@ === "C#" ```csharp title="linked_list.cs" - /* 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 */ + /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */ // 各ノードを初期化 ListNode n0 = new(1); ListNode n1 = new(3); @@ -242,7 +262,7 @@ === "Go" ```go title="linked_list.go" - /* 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 */ + /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */ // 各ノードを初期化 n0 := NewListNode(1) n1 := NewListNode(3) @@ -259,7 +279,7 @@ === "Swift" ```swift title="linked_list.swift" - /* 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 */ + /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */ // 各ノードを初期化 let n0 = ListNode(x: 1) let n1 = ListNode(x: 3) @@ -276,7 +296,7 @@ === "JS" ```javascript title="linked_list.js" - /* 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 */ + /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */ // 各ノードを初期化 const n0 = new ListNode(1); const n1 = new ListNode(3); @@ -293,7 +313,7 @@ === "TS" ```typescript title="linked_list.ts" - /* 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 */ + /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */ // 各ノードを初期化 const n0 = new ListNode(1); const n1 = new ListNode(3); @@ -310,7 +330,7 @@ === "Dart" ```dart title="linked_list.dart" - /* 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 */ + /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */\ // 各ノードを初期化 ListNode n0 = ListNode(1); ListNode n1 = ListNode(3); @@ -327,7 +347,7 @@ === "Rust" ```rust title="linked_list.rs" - /* 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 */ + /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */ // 各ノードを初期化 let n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None })); let n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None })); @@ -345,7 +365,7 @@ === "C" ```c title="linked_list.c" - /* 連結リストを初期化: 1 -> 3 -> 2 -> 5 -> 4 */ + /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */ // 各ノードを初期化 ListNode* n0 = newListNode(1); ListNode* n1 = newListNode(3); @@ -362,18 +382,50 @@ === "Kotlin" ```kotlin title="linked_list.kt" - + /* 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 */ + // 各ノードを初期化 + val n0 = ListNode(1) + val n1 = ListNode(3) + val n2 = ListNode(2) + val n3 = ListNode(5) + val n4 = ListNode(4) + // ノード間の参照を構築 + n0.next = n1; + n1.next = n2; + n2.next = n3; + n3.next = n4; ``` -配列全体は1つの変数です。例えば、配列`nums`には`nums[0]`、`nums[1]`などの要素が含まれますが、連結リストは複数の異なるノードオブジェクトで構成されています。**通常、連結リストはそのヘッドノードで参照されます**。例えば、前のコードスニペットの連結リストは`n0`として参照されます。 +=== "Ruby" + + ```ruby title="linked_list.rb" + # 連結リスト 1 -> 3 -> 2 -> 5 -> 4 を初期化 + # 各ノードを初期化 + n0 = ListNode.new(1) + n1 = ListNode.new(3) + n2 = ListNode.new(2) + n3 = ListNode.new(5) + n4 = ListNode.new(4) + # ノード間の参照を構築 + n0.next = n1 + n1.next = n2 + n2.next = n3 + n3.next = n4 + ``` + +??? pythontutor "可視化実行" + + https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%93%BE%E8%A1%A8%201%20-%3E%203%20-%3E%202%20-%3E%205%20-%3E%204%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%90%84%E4%B8%AA%E8%8A%82%E7%82%B9%0A%20%20%20%20n0%20%3D%20ListNode%281%29%0A%20%20%20%20n1%20%3D%20ListNode%283%29%0A%20%20%20%20n2%20%3D%20ListNode%282%29%0A%20%20%20%20n3%20%3D%20ListNode%285%29%0A%20%20%20%20n4%20%3D%20ListNode%284%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%0A%20%20%20%20n0.next%20%3D%20n1%0A%20%20%20%20n1.next%20%3D%20n2%0A%20%20%20%20n2.next%20%3D%20n3%0A%20%20%20%20n3.next%20%3D%20n4&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +配列全体は 1 つの変数であり、たとえば配列 `nums` には `nums[0]` や `nums[1]` などの要素が含まれます。一方、連結リストは複数の独立したノードオブジェクトで構成されます。**通常、先頭ノードを連結リストの代名詞として扱います**。たとえば上記のコードの連結リストは `n0` と表せます。 ### ノードの挿入 -連結リストにノードを挿入するのは非常に簡単です。下図に示すように、隣接する2つのノード`n0`と`n1`の間に新しいノード`P`を挿入することを目指すとします。**これは2つのノード参照(ポインタ)を変更するだけで実現でき**、時間計算量は$O(1)$です。 +連結リストへのノード挿入は非常に簡単です。下図に示すように、隣り合う 2 つのノード `n0` と `n1` の間に新しいノード `P` を挿入したいとします。**このとき 2 つのノードの参照(ポインタ)を変更するだけでよく**、時間計算量は $O(1)$ です。 -比較すると、配列に要素を挿入する時間計算量は$O(n)$であり、大量のデータを扱う場合には効率が悪くなります。 +これに対して、配列に要素を挿入する時間計算量は $O(n)$ であり、データ量が大きい場合の効率は低くなります。 -![連結リストノード挿入の例](linked_list.assets/linkedlist_insert_node.png) +![連結リストへのノード挿入例](linked_list.assets/linkedlist_insert_node.png) ```src [file]{linked_list}-[class]{}-[func]{insert} @@ -381,11 +433,11 @@ ### ノードの削除 -下図に示すように、連結リストからノードを削除することも非常に簡単で、**1つのノードの参照(ポインタ)を変更するだけです**。 +下図に示すように、連結リストでのノード削除も非常に簡単で、**1 つのノードの参照(ポインタ)を変更するだけで済みます**。 -重要な点は、ノード`P`が削除された後も`n1`を指し続けていることですが、連結リストの巡回中にはアクセスできなくなることです。これは事実上、`P`が連結リストの一部ではなくなったことを意味します。 +なお、削除操作が完了した後もノード `P` は依然として `n1` を指していますが、実際にはこの連結リストをたどっても `P` へはアクセスできません。つまり、`P` はすでにこの連結リストには属していません。 -![連結リストノードの削除](linked_list.assets/linkedlist_remove_node.png) +![連結リストのノード削除](linked_list.assets/linkedlist_remove_node.png) ```src [file]{linked_list}-[class]{}-[func]{remove} @@ -393,15 +445,15 @@ ### ノードへのアクセス -**連結リストでのノードへのアクセスは効率が悪いです**。前述したように、配列の任意の要素には$O(1)$時間でアクセスできます。対照的に、連結リストでは、プログラムはヘッドノードから開始して目的のノードが見つかるまで順次ノードを巡回する必要があります。つまり、連結リストの$i$番目のノードにアクセスするには、プログラムは$i - 1$個のノードを反復処理する必要があり、時間計算量は$O(n)$になります。 +**連結リストでノードにアクセスする効率は低い**です。前節で述べたように、配列では任意の要素へ $O(1)$ 時間でアクセスできます。これに対して連結リストでは、プログラムは先頭ノードから出発し、1 つずつ後ろへたどって目的のノードを見つける必要があります。つまり、連結リストの第 $i$ ノードにアクセスするには $i - 1$ 回のループが必要であり、時間計算量は $O(n)$ です。 ```src [file]{linked_list}-[class]{}-[func]{access} ``` -### ノードの検索 +### ノードの探索 -連結リストを巡回して、値が`target`に一致するノードを見つけ、連結リスト内でのそのノードのインデックスを出力します。この手順も線形検索の例です。対応するコードは以下のとおりです: +連結リストを走査し、その中から値が `target` のノードを探し、そのノードの連結リスト内でのインデックスを出力します。この処理も線形探索に属します。コードは次のとおりです。 ```src [file]{linked_list}-[class]{}-[func]{find} @@ -409,26 +461,26 @@ ## 配列 vs. 連結リスト -下表は配列と連結リストの特性をまとめ、様々な操作における効率も比較しています。それぞれが対照的な格納戦略を使用するため、それぞれの特性と操作効率は明確に対比されています。 +次の表は、配列と連結リストの各種特徴と操作効率をまとめたものです。両者は互いに逆の格納戦略を採用しているため、各種性質や操作効率にも対照的な特徴が現れます。

  配列と連結リストの効率比較

-| | 配列 | 連結リスト | -| ------------------ | ------------------------------------------------ | ----------------------- | -| 格納方式 | 連続メモリ空間 | 分散メモリ空間 | -| 容量拡張 | 固定長 | 柔軟な拡張 | -| メモリ効率 | 要素あたりのメモリ少、潜在的な空間の無駄 | 要素あたりのメモリ多 | -| 要素へのアクセス | $O(1)$ | $O(n)$ | -| 要素の追加 | $O(n)$ | $O(1)$ | -| 要素の削除 | $O(n)$ | $O(1)$ | +| | 配列 | 連結リスト | +| -------- | ------------------------------ | -------------- | +| 格納方式 | 連続したメモリ空間 | 分散したメモリ空間 | +| 容量拡張 | 長さは不変 | 柔軟に拡張可能 | +| メモリ効率 | 要素のメモリ消費は少ないが、空間を無駄にする可能性がある | 要素のメモリ消費が多い | +| 要素へのアクセス | $O(1)$ | $O(n)$ | +| 要素の追加 | $O(n)$ | $O(1)$ | +| 要素の削除 | $O(n)$ | $O(1)$ | -## 連結リストの一般的な種類 +## 一般的な連結リストの種類 -下図に示すように、連結リストには3つの一般的な種類があります。 +下図に示すように、一般的な連結リストの種類は 3 つあります。 -- **単方向連結リスト**:これは前述した標準的な連結リストです。単方向連結リストのノードには値と次のノードへの参照が含まれます。最初のノードはヘッドノードと呼ばれ、null(`None`)を指す最後のノードはテールノードです。 -- **循環連結リスト**:これは単方向連結リストのテールノードがヘッドノードを指してループを作ることで形成されます。循環連結リストでは、任意のノードがヘッドノードとして機能できます。 -- **双方向連結リスト**:単方向連結リストとは対照的に、双方向連結リストは2つの方向で参照を維持します。各ノードには後続者(次のノード)と前任者(前のノード)の両方への参照(ポインタ)が含まれます。双方向連結リストはどちらの方向にも巡回できるより多くの柔軟性を提供しますが、より多くのメモリ空間も消費します。 +- **単方向連結リスト**:前述した通常の連結リストのことです。単方向連結リストのノードは、値と次のノードを指す参照の 2 つのデータを含みます。最初のノードを先頭ノード、最後のノードを末尾ノードと呼び、末尾ノードは空 `None` を指します。 +- **循環連結リスト**:単方向連結リストの末尾ノードを先頭ノードへ向けると(先頭と末尾をつなぐと)、循環連結リストが得られます。循環連結リストでは、任意のノードを先頭ノードとみなせます。 +- **双方向連結リスト**:単方向連結リストと比べて、双方向連結リストは 2 方向の参照を記録します。双方向連結リストのノード定義には、後続ノード(次のノード)と前駆ノード(前のノード)を指す参照(ポインタ)が含まれます。単方向連結リストより柔軟で、2 方向に連結リストを走査できますが、そのぶん多くのメモリ空間を必要とします。 === "Python" @@ -436,9 +488,9 @@ class ListNode: """双方向連結リストノードクラス""" def __init__(self, val: int): - self.val: int = val # ノード値 + self.val: int = val # ノードの値 self.next: ListNode | None = None # 後続ノードへの参照 - self.prev: ListNode | None = None # 前任ノードへの参照 + self.prev: ListNode | None = None # 前駆ノードへの参照 ``` === "C++" @@ -446,9 +498,9 @@ ```cpp title="" /* 双方向連結リストノード構造体 */ struct ListNode { - int val; // ノード値 + int val; // ノードの値 ListNode *next; // 後続ノードへのポインタ - ListNode *prev; // 前任ノードへのポインタ + ListNode *prev; // 前駆ノードへのポインタ ListNode(int x) : val(x), next(nullptr), prev(nullptr) {} // コンストラクタ }; ``` @@ -458,9 +510,9 @@ ```java title="" /* 双方向連結リストノードクラス */ class ListNode { - int val; // ノード値 - ListNode next; // 次のノードへの参照 - ListNode prev; // 前任ノードへの参照 + int val; // ノードの値 + ListNode next; // 後続ノードへの参照 + ListNode prev; // 前駆ノードへの参照 ListNode(int x) { val = x; } // コンストラクタ } ``` @@ -470,9 +522,9 @@ ```csharp title="" /* 双方向連結リストノードクラス */ class ListNode(int x) { // コンストラクタ - int val = x; // ノード値 - ListNode next; // 次のノードへの参照 - ListNode prev; // 前任ノードへの参照 + int val = x; // ノードの値 + ListNode next; // 後続ノードへの参照 + ListNode prev; // 前駆ノードへの参照 } ``` @@ -481,12 +533,12 @@ ```go title="" /* 双方向連結リストノード構造体 */ type DoublyListNode struct { - Val int // ノード値 + Val int // ノードの値 Next *DoublyListNode // 後続ノードへのポインタ - Prev *DoublyListNode // 前任ノードへのポインタ + Prev *DoublyListNode // 前駆ノードへのポインタ } - // NewDoublyListNode 初期化 + // NewDoublyListNode の初期化 func NewDoublyListNode(val int) *DoublyListNode { return &DoublyListNode{ Val: val, @@ -501,9 +553,9 @@ ```swift title="" /* 双方向連結リストノードクラス */ class ListNode { - var val: Int // ノード値 - var next: ListNode? // 次のノードへの参照 - var prev: ListNode? // 前任ノードへの参照 + var val: Int // ノードの値 + var next: ListNode? // 後続ノードへの参照 + var prev: ListNode? // 前駆ノードへの参照 init(x: Int) { // コンストラクタ val = x @@ -517,9 +569,9 @@ /* 双方向連結リストノードクラス */ class ListNode { constructor(val, next, prev) { - this.val = val === undefined ? 0 : val; // ノード値 + this.val = val === undefined ? 0 : val; // ノードの値 this.next = next === undefined ? null : next; // 後続ノードへの参照 - this.prev = prev === undefined ? null : prev; // 前任ノードへの参照 + this.prev = prev === undefined ? null : prev; // 前駆ノードへの参照 } } ``` @@ -533,9 +585,9 @@ next: ListNode | null; prev: ListNode | null; constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) { - this.val = val === undefined ? 0 : val; // ノード値 + this.val = val === undefined ? 0 : val; // ノードの値 this.next = next === undefined ? null : next; // 後続ノードへの参照 - this.prev = prev === undefined ? null : prev; // 前任ノードへの参照 + this.prev = prev === undefined ? null : prev; // 前駆ノードへの参照 } } ``` @@ -545,9 +597,9 @@ ```dart title="" /* 双方向連結リストノードクラス */ class ListNode { - int val; // ノード値 - ListNode next; // 次のノードへの参照 - ListNode prev; // 前任ノードへの参照 + int val; // ノードの値 + ListNode? next; // 後続ノードへの参照 + ListNode? prev; // 前駆ノードへの参照 ListNode(this.val, [this.next, this.prev]); // コンストラクタ } ``` @@ -561,9 +613,9 @@ /* 双方向連結リストノード型 */ #[derive(Debug)] struct ListNode { - val: i32, // ノード値 + val: i32, // ノードの値 next: Option>>, // 後続ノードへのポインタ - prev: Option>>, // 前任ノードへのポインタ + prev: Option>>, // 前駆ノードへのポインタ } /* コンストラクタ */ @@ -583,14 +635,14 @@ ```c title="" /* 双方向連結リストノード構造体 */ typedef struct ListNode { - int val; // ノード値 + int val; // ノードの値 struct ListNode *next; // 後続ノードへのポインタ - struct ListNode *prev; // 前任ノードへのポインタ + struct ListNode *prev; // 前駆ノードへのポインタ } ListNode; /* コンストラクタ */ ListNode *newListNode(int val) { - ListNode *node, *next; + ListNode *node; node = (ListNode *) malloc(sizeof(ListNode)); node->val = val; node->next = NULL; @@ -602,26 +654,49 @@ === "Kotlin" ```kotlin title="" - + /* 双方向連結リストノードクラス */ + // コンストラクタ + class ListNode(x: Int) { + val _val: Int = x // ノードの値 + val next: ListNode? = null // 後続ノードへの参照 + val prev: ListNode? = null // 前駆ノードへの参照 + } ``` -![連結リストの一般的な種類](linked_list.assets/linkedlist_common_types.png) +=== "Ruby" + + ```ruby title="" + # 双方向連結リストノードクラス + class ListNode + attr_accessor :val # ノードの値 + attr_accessor :next # 後続ノードへの参照 + attr_accessor :prev # 前駆ノードへの参照 + + def initialize(val=0, next_node=nil, prev_node=nil) + @val = val + @next = next_node + @prev = prev_node + end + end + ``` + +![一般的な連結リストの種類](linked_list.assets/linkedlist_common_types.png) ## 連結リストの典型的な応用 -単方向連結リストは、スタック、キュー、ハッシュ表、グラフの実装によく使用されます。 +単方向連結リストは、スタック、キュー、ハッシュテーブル、グラフなどのデータ構造の実装によく用いられます。 -- **スタックとキュー**:単方向連結リストで、挿入と削除が同じ端で行われる場合、スタック(後入先出)のように動作します。逆に、挿入が一方の端で、削除がもう一方の端で行われる場合、キュー(先入先出)のように機能します。 -- **ハッシュ表**:連結リストは、ハッシュ衝突を解決する人気の方法である連鎖法で使用されます。ここでは、すべての衝突した要素が連結リストにグループ化されます。 -- **グラフ**:グラフ表現の標準的な方法である隣接リストは、各グラフ頂点を連結リストに関連付けます。このリストには、対応する頂点に接続された頂点を表す要素が含まれます。 +- **スタックとキュー**:挿入と削除の両方の操作を連結リストの一端で行うと、その性質は後入れ先出しとなり、スタックに対応します。挿入を連結リストの一端で行い、削除をもう一端で行うと、その性質は先入れ先出しとなり、キューに対応します。 +- **ハッシュテーブル**:連鎖アドレス法はハッシュ衝突を解決する主流の方式の 1 つであり、この方式では、衝突したすべての要素が 1 つの連結リストに格納されます。 +- **グラフ**:隣接リストはグラフを表現する一般的な方法の 1 つであり、グラフの各頂点は 1 つの連結リストに関連付けられます。連結リスト内の各要素は、その頂点に接続されたほかの頂点を表します。 -双方向連結リストは、前後の要素への高速アクセスが必要なシナリオに最適です。 +双方向連結リストは、前後の要素をすばやく見つける必要がある場面でよく用いられます。 -- **高度なデータ構造**:赤黒木やB木などの構造では、ノードの親へのアクセスが重要です。これは各ノードに親ノードへの参照を組み込むことで実現され、双方向連結リストに似ています。 -- **ブラウザ履歴**:Webブラウザでは、双方向連結リストにより、ユーザーが前進または後退ボタンをクリックしたときの訪問ページの履歴ナビゲーションが容易になります。 -- **LRUアルゴリズム**:双方向連結リストは、最近最少使用(LRU)キャッシュ削除アルゴリズムに適しており、最近最少使用データの迅速な識別と、高速なノード追加・削除を可能にします。 +- **高度なデータ構造**:たとえば赤黒木や B 木では、ノードの親ノードへアクセスする必要があります。これは、ノード内に親ノードを指す参照を保持することで実現でき、双方向連結リストに似ています。 +- **ブラウザ履歴**:Web ブラウザでユーザーが進むボタンや戻るボタンをクリックしたとき、ブラウザはユーザーが訪れた前後のページを知る必要があります。双方向連結リストの性質によって、この操作は簡単になります。 +- **LRU アルゴリズム**:キャッシュ淘汰(LRU)アルゴリズムでは、最近最も使用されていないデータをすばやく見つける必要があり、さらにノードの高速な追加と削除も必要です。そのため、双方向連結リストが非常に適しています。 -循環連結リストは、オペレーティングシステムでのリソーススケジューリングなど、周期的な操作が必要なアプリケーションに最適です。 +循環連結リストは、オペレーティングシステムのリソーススケジューリングのように、周期的な操作が必要な場面でよく用いられます。 -- **ラウンドロビンスケジューリングアルゴリズム**:オペレーティングシステムでは、ラウンドロビンスケジューリングアルゴリズムは一般的なCPUスケジューリング方法であり、プロセスのグループを循環する必要があります。各プロセスにはタイムスライスが割り当てられ、期限切れになるとCPUは次のプロセスに回転します。この循環操作は循環連結リストを使用して効率的に実現でき、すべてのプロセス間で公平かつ時分割システムを可能にします。 -- **データバッファ**:循環連結リストは、オーディオやビデオプレーヤーなどのデータバッファでも使用され、データストリームが複数のバッファブロックに分割され、シームレスな再生のために循環方式で配置されます。 +- **ラウンドロビン時間片スケジューリングアルゴリズム**:オペレーティングシステムにおいて、ラウンドロビン時間片スケジューリングは一般的な CPU スケジューリングアルゴリズムであり、一連のプロセスを循環的に処理する必要があります。各プロセスには 1 つの時間片が割り当てられ、その時間片を使い切ると、CPU は次のプロセスへ切り替わります。この循環操作は、循環連結リストで実現できます。 +- **データバッファ**:一部のデータバッファ実装でも、循環連結リストが使われることがあります。たとえば音声・動画プレーヤーでは、データストリームを複数のバッファブロックに分割して循環連結リストへ格納し、シームレス再生を実現できます。 diff --git a/ja/docs/chapter_array_and_linkedlist/list.md b/ja/docs/chapter_array_and_linkedlist/list.md index a26192ed9..88314bbee 100644 --- a/ja/docs/chapter_array_and_linkedlist/list.md +++ b/ja/docs/chapter_array_and_linkedlist/list.md @@ -1,21 +1,21 @@ # リスト -リストは、要素へのアクセス、変更、追加、削除、走査などの操作をサポートする、順序付けられた要素のコレクションを表す抽象的なデータ構造の概念であり、ユーザーが容量制限を考慮する必要がありません。リストは連結リストまたは配列に基づいて実装できます。 +リスト(list)は抽象的なデータ構造の概念であり、要素の順序付き集合を表す。要素のアクセス、更新、追加、削除、走査などの操作をサポートし、利用者は容量制限の問題を考慮する必要がない。リストは連結リストまたは配列に基づいて実装できる。 -- 連結リストは本質的にリストとして機能し、要素の追加、削除、検索、変更の操作をサポートし、サイズを動的に調整する柔軟性があります。 -- 配列もこれらの操作をサポートしますが、長さが不変であるため、長さ制限のあるリストと考えることができます。 +- 連結リストは本質的にリストと見なすことができ、要素の追加・削除・参照・更新をサポートし、柔軟に動的拡張できる。 +- 配列も要素の追加・削除・参照・更新をサポートするが、長さが不変であるため、長さ制限のあるリストとしか見なせない。 -配列を使用してリストを実装する場合、**長さの不変性によりリストの実用性が低下します**。これは、事前に格納するデータ量を予測することが困難な場合が多く、適切なリスト長を選択することが困難であるためです。長さが小さすぎると要件を満たさない可能性があり、大きすぎるとメモリ空間を無駄にする可能性があります。 +配列でリストを実装する場合、**長さが不変である性質によってリストの実用性が低下する**。これは、通常は事前にどれだけのデータを格納する必要があるかを決められず、適切なリスト長を選びにくいためである。長さが小さすぎると利用要件を満たせない可能性が高く、大きすぎるとメモリ空間の浪費を招く。 -この問題を解決するために、動的配列を使用してリストを実装できます。これは配列の利点を継承し、プログラム実行中に動的に拡張できます。 +この問題を解決するために、動的配列(dynamic array)を用いてリストを実装できる。これは配列の各種利点を引き継ぎつつ、プログラム実行中に動的な拡張を行える。 -実際、**多くのプログラミング言語の標準ライブラリは動的配列を使用してリストを実装しています**。例えば、Pythonの`list`、Javaの`ArrayList`、C++の`vector`、C#の`List`などです。以下の議論では、「リスト」と「動的配列」を同義の概念として扱います。 +実際には、**多くのプログラミング言語の標準ライブラリが提供するリストは動的配列に基づいて実装されている**。たとえば、Python の `list` 、Java の `ArrayList` 、C++ の `vector` 、C# の `List` などである。以降の議論では、「リスト」と「動的配列」を同じ概念として扱う。 -## リストの一般的な操作 +## リストの基本操作 ### リストの初期化 -通常、「初期値なし」と「初期値あり」の2つの初期化方法を使用します。 +通常は「初期値なし」と「初期値あり」の 2 つの初期化方法を用いる。 === "Python" @@ -31,7 +31,7 @@ ```cpp title="list.cpp" /* リストを初期化 */ - // 注意: C++では、vectorがここで説明されているnumsに相当します + // なお、C++ では vector が本稿でいう nums に相当する // 初期値なし vector nums1; // 初期値あり @@ -44,7 +44,7 @@ /* リストを初期化 */ // 初期値なし List nums1 = new ArrayList<>(); - // 初期値あり(要素型はint[]のラッパークラスInteger[]である必要があります) + // 初期値あり(配列の要素型は int[] のラッパークラスである Integer[] である必要があることに注意) Integer[] numbers = new Integer[] { 1, 3, 2, 5, 4 }; List nums = new ArrayList<>(Arrays.asList(numbers)); ``` @@ -123,133 +123,168 @@ === "C" ```c title="list.c" - // Cは組み込みの動的配列を提供していません + // C には組み込みの動的配列がない ``` === "Kotlin" ```kotlin title="list.kt" - + /* リストを初期化 */ + // 初期値なし + var nums1 = listOf() + // 初期値あり + var numbers = arrayOf(1, 3, 2, 5, 4) + var nums = numbers.toMutableList() ``` +=== "Ruby" + + ```ruby title="list.rb" + # リストを初期化 + # 初期値なし + nums1 = [] + # 初期値あり + nums = [1, 3, 2, 5, 4] + ``` + +??? pythontutor "可視化実行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20%23%20%E6%97%A0%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums1%20%3D%20%5B%5D%0A%20%20%20%20%23%20%E6%9C%89%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D&cumulative=false&curInstr=4&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + ### 要素へのアクセス -リストは本質的に配列であるため、$O(1)$時間で要素にアクセスし更新することができ、非常に効率的です。 +リストの本質は配列であるため、要素へのアクセスと更新は $O(1)$ 時間で行え、効率が高い。 === "Python" ```python title="list.py" # 要素にアクセス - num: int = nums[1] # インデックス1の要素にアクセス + num: int = nums[1] # インデックス 1 の要素にアクセス # 要素を更新 - nums[1] = 0 # インデックス1の要素を0に更新 + nums[1] = 0 # インデックス 1 の要素を 0 に更新 ``` === "C++" ```cpp title="list.cpp" /* 要素にアクセス */ - int num = nums[1]; // インデックス1の要素にアクセス + int num = nums[1]; // インデックス 1 の要素にアクセス /* 要素を更新 */ - nums[1] = 0; // インデックス1の要素を0に更新 + nums[1] = 0; // インデックス 1 の要素を 0 に更新 ``` === "Java" ```java title="list.java" /* 要素にアクセス */ - int num = nums.get(1); // インデックス1の要素にアクセス + int num = nums.get(1); // インデックス 1 の要素にアクセス /* 要素を更新 */ - nums.set(1, 0); // インデックス1の要素を0に更新 + nums.set(1, 0); // インデックス 1 の要素を 0 に更新 ``` === "C#" ```csharp title="list.cs" /* 要素にアクセス */ - int num = nums[1]; // インデックス1の要素にアクセス + int num = nums[1]; // インデックス 1 の要素にアクセス /* 要素を更新 */ - nums[1] = 0; // インデックス1の要素を0に更新 + nums[1] = 0; // インデックス 1 の要素を 0 に更新 ``` === "Go" ```go title="list_test.go" /* 要素にアクセス */ - num := nums[1] // インデックス1の要素にアクセス + num := nums[1] // インデックス 1 の要素にアクセス /* 要素を更新 */ - nums[1] = 0 // インデックス1の要素を0に更新 + nums[1] = 0 // インデックス 1 の要素を 0 に更新 ``` === "Swift" ```swift title="list.swift" /* 要素にアクセス */ - let num = nums[1] // インデックス1の要素にアクセス + let num = nums[1] // インデックス 1 の要素にアクセス /* 要素を更新 */ - nums[1] = 0 // インデックス1の要素を0に更新 + nums[1] = 0 // インデックス 1 の要素を 0 に更新 ``` === "JS" ```javascript title="list.js" /* 要素にアクセス */ - const num = nums[1]; // インデックス1の要素にアクセス + const num = nums[1]; // インデックス 1 の要素にアクセス /* 要素を更新 */ - nums[1] = 0; // インデックス1の要素を0に更新 + nums[1] = 0; // インデックス 1 の要素を 0 に更新 ``` === "TS" ```typescript title="list.ts" /* 要素にアクセス */ - const num: number = nums[1]; // インデックス1の要素にアクセス + const num: number = nums[1]; // インデックス 1 の要素にアクセス /* 要素を更新 */ - nums[1] = 0; // インデックス1の要素を0に更新 + nums[1] = 0; // インデックス 1 の要素を 0 に更新 ``` === "Dart" ```dart title="list.dart" /* 要素にアクセス */ - int num = nums[1]; // インデックス1の要素にアクセス + int num = nums[1]; // インデックス 1 の要素にアクセス /* 要素を更新 */ - nums[1] = 0; // インデックス1の要素を0に更新 + nums[1] = 0; // インデックス 1 の要素を 0 に更新 ``` === "Rust" ```rust title="list.rs" /* 要素にアクセス */ - let num: i32 = nums[1]; // インデックス1の要素にアクセス + let num: i32 = nums[1]; // インデックス 1 の要素にアクセス /* 要素を更新 */ - nums[1] = 0; // インデックス1の要素を0に更新 + nums[1] = 0; // インデックス 1 の要素を 0 に更新 ``` === "C" ```c title="list.c" - // Cは組み込みの動的配列を提供していません + // C には組み込みの動的配列がない ``` === "Kotlin" ```kotlin title="list.kt" - + /* 要素にアクセス */ + val num = nums[1] // インデックス 1 の要素にアクセス + /* 要素を更新 */ + nums[1] = 0 // インデックス 1 の要素を 0 に更新 ``` +=== "Ruby" + + ```ruby title="list.rb" + # 要素にアクセス + num = nums[1] # インデックス 1 の要素にアクセス + # 要素を更新 + nums[1] = 0 # インデックス 1 の要素を 0 に更新 + ``` + +??? pythontutor "可視化実行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20num%20%3D%20nums%5B1%5D%20%20%23%20%E8%AE%BF%E9%97%AE%E7%B4%A2%E5%BC%95%201%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%0A%0A%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums%5B1%5D%20%3D%200%20%20%20%20%23%20%E5%B0%86%E7%B4%A2%E5%BC%95%201%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0%E6%9B%B4%E6%96%B0%E4%B8%BA%200&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + ### 要素の挿入と削除 -配列と比較して、リストは要素の追加と削除においてより柔軟性を提供します。リストの末尾への要素追加は$O(1)$操作ですが、リストの他の場所での要素の挿入と削除の効率は配列と同じままで、時間計算量は$O(n)$です。 +配列と比べて、リストでは要素を自由に追加・削除できる。リスト末尾への要素追加の時間計算量は $O(1)$ だが、要素の挿入と削除の効率は依然として配列と同じで、時間計算量は $O(n)$ である。 === "Python" @@ -264,11 +299,11 @@ nums.append(5) nums.append(4) - # 中間に要素を挿入 - nums.insert(3, 6) # インデックス3に数値6を挿入 + # 途中に要素を挿入 + nums.insert(3, 6) # インデックス 3 に数値 6 を挿入 # 要素を削除 - nums.pop(3) # インデックス3の要素を削除 + nums.pop(3) # インデックス 3 の要素を削除 ``` === "C++" @@ -284,11 +319,11 @@ nums.push_back(5); nums.push_back(4); - /* 中間に要素を挿入 */ - nums.insert(nums.begin() + 3, 6); // インデックス3に数値6を挿入 + /* 途中に要素を挿入 */ + nums.insert(nums.begin() + 3, 6); // インデックス 3 に数値 6 を挿入 /* 要素を削除 */ - nums.erase(nums.begin() + 3); // インデックス3の要素を削除 + nums.erase(nums.begin() + 3); // インデックス 3 の要素を削除 ``` === "Java" @@ -304,11 +339,11 @@ nums.add(5); nums.add(4); - /* 中間に要素を挿入 */ - nums.add(3, 6); // インデックス3に数値6を挿入 + /* 途中に要素を挿入 */ + nums.add(3, 6); // インデックス 3 に数値 6 を挿入 /* 要素を削除 */ - nums.remove(3); // インデックス3の要素を削除 + nums.remove(3); // インデックス 3 の要素を削除 ``` === "C#" @@ -324,11 +359,11 @@ nums.Add(5); nums.Add(4); - /* 中間に要素を挿入 */ - nums.Insert(3, 6); + /* 途中に要素を挿入 */ + nums.Insert(3, 6); // インデックス 3 に数値 6 を挿入 /* 要素を削除 */ - nums.RemoveAt(3); + nums.RemoveAt(3); // インデックス 3 の要素を削除 ``` === "Go" @@ -344,11 +379,11 @@ nums = append(nums, 5) nums = append(nums, 4) - /* 中間に要素を挿入 */ - nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // インデックス3に数値6を挿入 + /* 途中に要素を挿入 */ + nums = append(nums[:3], append([]int{6}, nums[3:]...)...) // インデックス 3 に数値 6 を挿入 /* 要素を削除 */ - nums = append(nums[:3], nums[4:]...) // インデックス3の要素を削除 + nums = append(nums[:3], nums[4:]...) // インデックス 3 の要素を削除 ``` === "Swift" @@ -364,11 +399,11 @@ nums.append(5) nums.append(4) - /* 中間に要素を挿入 */ - nums.insert(6, at: 3) // インデックス3に数値6を挿入 + /* 途中に要素を挿入 */ + nums.insert(6, at: 3) // インデックス 3 に数値 6 を挿入 /* 要素を削除 */ - nums.remove(at: 3) // インデックス3の要素を削除 + nums.remove(at: 3) // インデックス 3 の要素を削除 ``` === "JS" @@ -384,11 +419,11 @@ nums.push(5); nums.push(4); - /* 中間に要素を挿入 */ - nums.splice(3, 0, 6); + /* 途中に要素を挿入 */ + nums.splice(3, 0, 6); // インデックス 3 に数値 6 を挿入 /* 要素を削除 */ - nums.splice(3, 1); + nums.splice(3, 1); // インデックス 3 の要素を削除 ``` === "TS" @@ -404,11 +439,11 @@ nums.push(5); nums.push(4); - /* 中間に要素を挿入 */ - nums.splice(3, 0, 6); + /* 途中に要素を挿入 */ + nums.splice(3, 0, 6); // インデックス 3 に数値 6 を挿入 /* 要素を削除 */ - nums.splice(3, 1); + nums.splice(3, 1); // インデックス 3 の要素を削除 ``` === "Dart" @@ -424,11 +459,11 @@ nums.add(5); nums.add(4); - /* 中間に要素を挿入 */ - nums.insert(3, 6); // インデックス3に数値6を挿入 + /* 途中に要素を挿入 */ + nums.insert(3, 6); // インデックス 3 に数値 6 を挿入 /* 要素を削除 */ - nums.removeAt(3); // インデックス3の要素を削除 + nums.removeAt(3); // インデックス 3 の要素を削除 ``` === "Rust" @@ -444,38 +479,76 @@ nums.push(5); nums.push(4); - /* 中間に要素を挿入 */ - nums.insert(3, 6); // インデックス3に数値6を挿入 + /* 途中に要素を挿入 */ + nums.insert(3, 6); // インデックス 3 に数値 6 を挿入 /* 要素を削除 */ - nums.remove(3); // インデックス3の要素を削除 + nums.remove(3); // インデックス 3 の要素を削除 ``` === "C" ```c title="list.c" - // Cは組み込みの動的配列を提供していません + // C には組み込みの動的配列がない ``` === "Kotlin" ```kotlin title="list.kt" + /* リストをクリア */ + nums.clear(); + /* 末尾に要素を追加 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + + /* 途中に要素を挿入 */ + nums.add(3, 6); // インデックス 3 に数値 6 を挿入 + + /* 要素を削除 */ + nums.remove(3); // インデックス 3 の要素を削除 ``` -### リストの反復 +=== "Ruby" -配列と同様に、リストはインデックスを使用して反復することも、各要素を直接反復することもできます。 + ```ruby title="list.rb" + # リストをクリア + nums.clear + + # 末尾に要素を追加 + nums << 1 + nums << 3 + nums << 2 + nums << 5 + nums << 4 + + # 途中に要素を挿入 + nums.insert(3, 6) # インデックス 3 に数値 6 を挿入 + + # 要素を削除 + nums.delete_at(3) # インデックス 3 の要素を削除 + ``` + +??? pythontutor "可視化実行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E6%9C%89%E5%88%9D%E5%A7%8B%E5%80%BC%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B8%85%E7%A9%BA%E5%88%97%E8%A1%A8%0A%20%20%20%20nums.clear%28%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%9C%A8%E5%B0%BE%E9%83%A8%E6%B7%BB%E5%8A%A0%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.append%281%29%0A%20%20%20%20nums.append%283%29%0A%20%20%20%20nums.append%282%29%0A%20%20%20%20nums.append%285%29%0A%20%20%20%20nums.append%284%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%9C%A8%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.insert%283,%206%29%20%20%23%20%E5%9C%A8%E7%B4%A2%E5%BC%95%203%20%E5%A4%84%E6%8F%92%E5%85%A5%E6%95%B0%E5%AD%97%206%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E5%85%83%E7%B4%A0%0A%20%20%20%20nums.pop%283%29%20%20%20%20%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E7%B4%A2%E5%BC%95%203%20%E5%A4%84%E7%9A%84%E5%85%83%E7%B4%A0&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### リストの走査 + +配列と同様に、リストもインデックスに基づいて走査することも、各要素を直接走査することもできる。 === "Python" ```python title="list.py" - # インデックスでリストを反復 + # インデックスでリストを走査 count = 0 for i in range(len(nums)): count += nums[i] - # リスト要素を直接反復 + # リストの要素を直接走査 for num in nums: count += num ``` @@ -483,13 +556,13 @@ === "C++" ```cpp title="list.cpp" - /* インデックスでリストを反復 */ + /* インデックスでリストを走査 */ int count = 0; for (int i = 0; i < nums.size(); i++) { count += nums[i]; } - /* リスト要素を直接反復 */ + /* リストの要素を直接走査 */ count = 0; for (int num : nums) { count += num; @@ -499,13 +572,13 @@ === "Java" ```java title="list.java" - /* インデックスでリストを反復 */ + /* インデックスでリストを走査 */ int count = 0; for (int i = 0; i < nums.size(); i++) { count += nums.get(i); } - /* リスト要素を直接反復 */ + /* リストの要素を直接走査 */ for (int num : nums) { count += num; } @@ -514,13 +587,13 @@ === "C#" ```csharp title="list.cs" - /* インデックスでリストを反復 */ + /* インデックスでリストを走査 */ int count = 0; for (int i = 0; i < nums.Count; i++) { count += nums[i]; } - /* リスト要素を直接反復 */ + /* リストの要素を直接走査 */ count = 0; foreach (int num in nums) { count += num; @@ -530,13 +603,13 @@ === "Go" ```go title="list_test.go" - /* インデックスでリストを反復 */ + /* インデックスでリストを走査 */ count := 0 for i := 0; i < len(nums); i++ { count += nums[i] } - /* リスト要素を直接反復 */ + /* リストの要素を直接走査 */ count = 0 for _, num := range nums { count += num @@ -546,13 +619,13 @@ === "Swift" ```swift title="list.swift" - /* インデックスでリストを反復 */ + /* インデックスでリストを走査 */ var count = 0 for i in nums.indices { count += nums[i] } - /* リスト要素を直接反復 */ + /* リストの要素を直接走査 */ count = 0 for num in nums { count += num @@ -562,13 +635,13 @@ === "JS" ```javascript title="list.js" - /* インデックスでリストを反復 */ + /* インデックスでリストを走査 */ let count = 0; for (let i = 0; i < nums.length; i++) { count += nums[i]; } - /* リスト要素を直接反復 */ + /* リストの要素を直接走査 */ count = 0; for (const num of nums) { count += num; @@ -578,13 +651,13 @@ === "TS" ```typescript title="list.ts" - /* インデックスでリストを反復 */ + /* インデックスでリストを走査 */ let count = 0; for (let i = 0; i < nums.length; i++) { count += nums[i]; } - /* リスト要素を直接反復 */ + /* リストの要素を直接走査 */ count = 0; for (const num of nums) { count += num; @@ -594,13 +667,13 @@ === "Dart" ```dart title="list.dart" - /* インデックスでリストを反復 */ + /* インデックスでリストを走査 */ int count = 0; for (var i = 0; i < nums.length; i++) { count += nums[i]; } - /* リスト要素を直接反復 */ + /* リストの要素を直接走査 */ count = 0; for (var num in nums) { count += num; @@ -610,13 +683,13 @@ === "Rust" ```rust title="list.rs" - // インデックスでリストを反復 + // インデックスでリストを走査 let mut _count = 0; for i in 0..nums.len() { _count += nums[i]; } - // リスト要素を直接反復 + // リストの要素を直接走査 _count = 0; for num in &nums { _count += num; @@ -626,96 +699,125 @@ === "C" ```c title="list.c" - // Cは組み込みの動的配列を提供していません + // C には組み込みの動的配列がない ``` === "Kotlin" ```kotlin title="list.kt" + /* インデックスでリストを走査 */ + var count = 0 + for (i in nums.indices) { + count += nums[i] + } + /* リストの要素を直接走査 */ + for (num in nums) { + count += num + } ``` +=== "Ruby" + + ```ruby title="list.rb" + # インデックスでリストを走査 + count = 0 + for i in 0...nums.length + count += nums[i] + end + + # リストの要素を直接走査 + count = 0 + for num in nums + count += num + end + ``` + +??? pythontutor "可視化実行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%80%9A%E8%BF%87%E7%B4%A2%E5%BC%95%E9%81%8D%E5%8E%86%E5%88%97%E8%A1%A8%0A%20%20%20%20count%20%3D%200%0A%20%20%20%20for%20i%20in%20range%28len%28nums%29%29%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20nums%5Bi%5D%0A%0A%20%20%20%20%23%20%E7%9B%B4%E6%8E%A5%E9%81%8D%E5%8E%86%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%0A%20%20%20%20for%20num%20in%20nums%3A%0A%20%20%20%20%20%20%20%20count%20%2B%3D%20num&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + ### リストの連結 -新しいリスト`nums1`が与えられたとき、それを元のリストの末尾に追加できます。 +新しいリスト `nums1` が与えられたとき、それを元のリストの末尾に連結できる。 === "Python" ```python title="list.py" - # 2つのリストを連結 + # 2 つのリストを連結 nums1: list[int] = [6, 8, 7, 10, 9] - nums += nums1 # nums1をnumsの末尾に連結 + nums += nums1 # リスト nums1 を nums の後ろに連結 ``` === "C++" ```cpp title="list.cpp" - /* 2つのリストを連結 */ + /* 2 つのリストを連結 */ vector nums1 = { 6, 8, 7, 10, 9 }; - // nums1をnumsの末尾に連結 + // リスト nums1 を nums の後ろに連結 nums.insert(nums.end(), nums1.begin(), nums1.end()); ``` === "Java" ```java title="list.java" - /* 2つのリストを連結 */ + /* 2 つのリストを連結 */ List nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); - nums.addAll(nums1); // nums1をnumsの末尾に連結 + nums.addAll(nums1); // リスト nums1 を nums の後ろに連結 ``` === "C#" ```csharp title="list.cs" - /* 2つのリストを連結 */ + /* 2 つのリストを連結 */ List nums1 = [6, 8, 7, 10, 9]; - nums.AddRange(nums1); // nums1をnumsの末尾に連結 + nums.AddRange(nums1); // リスト nums1 を nums の後ろに連結 ``` === "Go" ```go title="list_test.go" - /* 2つのリストを連結 */ + /* 2 つのリストを連結 */ nums1 := []int{6, 8, 7, 10, 9} - nums = append(nums, nums1...) // nums1をnumsの末尾に連結 + nums = append(nums, nums1...) // リスト nums1 を nums の後ろに連結 ``` === "Swift" ```swift title="list.swift" - /* 2つのリストを連結 */ + /* 2 つのリストを連結 */ let nums1 = [6, 8, 7, 10, 9] - nums.append(contentsOf: nums1) // nums1をnumsの末尾に連結 + nums.append(contentsOf: nums1) // リスト nums1 を nums の後ろに連結 ``` === "JS" ```javascript title="list.js" - /* 2つのリストを連結 */ + /* 2 つのリストを連結 */ const nums1 = [6, 8, 7, 10, 9]; - nums.push(...nums1); // nums1をnumsの末尾に連結 + nums.push(...nums1); // リスト nums1 を nums の後ろに連結 ``` === "TS" ```typescript title="list.ts" - /* 2つのリストを連結 */ + /* 2 つのリストを連結 */ const nums1: number[] = [6, 8, 7, 10, 9]; - nums.push(...nums1); // nums1をnumsの末尾に連結 + nums.push(...nums1); // リスト nums1 を nums の後ろに連結 ``` === "Dart" ```dart title="list.dart" - /* 2つのリストを連結 */ + /* 2 つのリストを連結 */ List nums1 = [6, 8, 7, 10, 9]; - nums.addAll(nums1); // nums1をnumsの末尾に連結 + nums.addAll(nums1); // リスト nums1 を nums の後ろに連結 ``` === "Rust" ```rust title="list.rs" - /* 2つのリストを連結 */ + /* 2 つのリストを連結 */ let nums1: Vec = vec![6, 8, 7, 10, 9]; nums.extend(nums1); ``` @@ -723,110 +825,136 @@ === "C" ```c title="list.c" - // Cは組み込みの動的配列を提供していません + // C には組み込みの動的配列がない ``` === "Kotlin" ```kotlin title="list.kt" - + /* 2 つのリストを連結 */ + val nums1 = intArrayOf(6, 8, 7, 10, 9).toMutableList() + nums.addAll(nums1) // リスト nums1 を nums の後ろに連結 ``` -### リストのソート +=== "Ruby" -リストがソートされると、「二分探索」や「双ポインタ」アルゴリズムなど、配列関連のアルゴリズム問題でよく使用されるアルゴリズムを使用できます。 + ```ruby title="list.rb" + # 2 つのリストを連結 + nums1 = [6, 8, 7, 10, 9] + nums += nums1 + ``` + +??? pythontutor "可視化実行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%8B%BC%E6%8E%A5%E4%B8%A4%E4%B8%AA%E5%88%97%E8%A1%A8%0A%20%20%20%20nums1%20%3D%20%5B6,%208,%207,%2010,%209%5D%0A%20%20%20%20nums%20%2B%3D%20nums1%20%20%23%20%E5%B0%86%E5%88%97%E8%A1%A8%20nums1%20%E6%8B%BC%E6%8E%A5%E5%88%B0%20nums%20%E4%B9%8B%E5%90%8E&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +### リストをソート + +リストのソートが完了すると、配列系アルゴリズム問題でよく問われる「二分探索」や「両ポインタ」アルゴリズムを使えるようになる。 === "Python" ```python title="list.py" # リストをソート - nums.sort() # ソート後、リスト要素は昇順になります + nums.sort() # ソート後、リスト要素は小さい順に並ぶ ``` === "C++" ```cpp title="list.cpp" /* リストをソート */ - sort(nums.begin(), nums.end()); // ソート後、リスト要素は昇順になります + sort(nums.begin(), nums.end()); // ソート後、リスト要素は小さい順に並ぶ ``` === "Java" ```java title="list.java" /* リストをソート */ - Collections.sort(nums); // ソート後、リスト要素は昇順になります + Collections.sort(nums); // ソート後、リスト要素は小さい順に並ぶ ``` === "C#" ```csharp title="list.cs" /* リストをソート */ - nums.Sort(); // ソート後、リスト要素は昇順になります + nums.Sort(); // ソート後、リスト要素は小さい順に並ぶ ``` === "Go" ```go title="list_test.go" /* リストをソート */ - sort.Ints(nums) // ソート後、リスト要素は昇順になります + sort.Ints(nums) // ソート後、リスト要素は小さい順に並ぶ ``` === "Swift" ```swift title="list.swift" /* リストをソート */ - nums.sort() // ソート後、リスト要素は昇順になります + nums.sort() // ソート後、リスト要素は小さい順に並ぶ ``` === "JS" ```javascript title="list.js" /* リストをソート */ - nums.sort((a, b) => a - b); // ソート後、リスト要素は昇順になります + nums.sort((a, b) => a - b); // ソート後、リスト要素は小さい順に並ぶ ``` === "TS" ```typescript title="list.ts" /* リストをソート */ - nums.sort((a, b) => a - b); // ソート後、リスト要素は昇順になります + nums.sort((a, b) => a - b); // ソート後、リスト要素は小さい順に並ぶ ``` === "Dart" ```dart title="list.dart" /* リストをソート */ - nums.sort(); // ソート後、リスト要素は昇順になります + nums.sort(); // ソート後、リスト要素は小さい順に並ぶ ``` === "Rust" ```rust title="list.rs" /* リストをソート */ - nums.sort(); // ソート後、リスト要素は昇順になります + nums.sort(); // ソート後、リスト要素は小さい順に並ぶ ``` === "C" ```c title="list.c" - // Cは組み込みの動的配列を提供していません + // C には組み込みの動的配列がない ``` === "Kotlin" ```kotlin title="list.kt" - + /* リストをソート */ + nums.sort() // ソート後、リスト要素は小さい順に並ぶ ``` +=== "Ruby" + + ```ruby title="list.rb" + # リストをソート + nums = nums.sort { |a, b| a <=> b } # ソート後、リスト要素は小さい順に並ぶ + ``` + +??? pythontutor "可視化実行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8%0A%20%20%20%20nums%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%8E%92%E5%BA%8F%E5%88%97%E8%A1%A8%0A%20%20%20%20nums.sort%28%29%20%20%23%20%E6%8E%92%E5%BA%8F%E5%90%8E%EF%BC%8C%E5%88%97%E8%A1%A8%E5%85%83%E7%B4%A0%E4%BB%8E%E5%B0%8F%E5%88%B0%E5%A4%A7%E6%8E%92%E5%88%97&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + ## リストの実装 -多くのプログラミング言語には、Java、C++、Pythonなどを含む組み込みリストが付属しています。それらの実装は、初期容量や拡張係数などの様々なパラメータを慎重に考慮した設定で、複雑になりがちです。興味のある読者は、さらなる学習のためにソースコードを調べることができます。 +多くのプログラミング言語にはリストが組み込まれており、たとえば Java、C++、Python などがある。それらの実装は比較的複雑で、初期容量や拡張倍率など各種パラメータの設定もよく考えられている。興味があればソースコードを参照して学べる。 -リストがどのように動作するかの理解を深めるために、3つの重要な設計側面に焦点を当てて、簡略化されたリストの実装を試みます: +リストの動作原理への理解を深めるため、ここでは簡易版のリストを実装し、以下の 3 つの設計ポイントを含める。 -- **初期容量**:配列に合理的な初期容量を選択します。この例では、初期容量として10を選択します。 -- **サイズ記録**:リスト内の現在の要素数を記録する変数`size`を宣言し、要素の挿入と削除でリアルタイムに更新します。この変数により、リストの末尾を特定し、拡張が必要かどうかを判断できます。 -- **拡張メカニズム**:要素挿入時にリストが満杯に達した場合、拡張プロセスが必要です。これには拡張係数に基づいてより大きな配列を作成し、現在の配列からすべての要素を新しい配列に転送することが含まれます。この例では、拡張のたびに配列サイズを2倍にすることを規定します。 +- **初期容量**:妥当な配列の初期容量を選ぶ。この例では 10 を初期容量として選ぶ。 +- **要素数の記録**:`size` という変数を宣言して、現在のリスト要素数を記録し、要素の挿入と削除に応じてリアルタイムに更新する。この変数により、リスト末尾の位置を特定し、拡張が必要かどうかを判断できる。 +- **拡張機構**:要素を挿入する時点でリスト容量がいっぱいなら、拡張が必要になる。まず拡張倍率に応じてより大きな配列を作成し、次に現在の配列の全要素を順に新しい配列へ移す。この例では、配列を毎回以前の 2 倍に拡張する。 ```src [file]{my_list}-[class]{my_list}-[func]{} diff --git a/ja/docs/chapter_array_and_linkedlist/ram_and_cache.md b/ja/docs/chapter_array_and_linkedlist/ram_and_cache.md index 88a2323c9..18629360d 100644 --- a/ja/docs/chapter_array_and_linkedlist/ram_and_cache.md +++ b/ja/docs/chapter_array_and_linkedlist/ram_and_cache.md @@ -1,71 +1,71 @@ # メモリとキャッシュ * -この章の最初の2つのセクションでは、「連続格納」と「分散格納」をそれぞれ表現する2つの基本的なデータ構造である配列と連結リストを探究しました。 +本章の前二節では、配列と連結リストという二つの基礎的かつ重要なデータ構造を扱いました。これらはそれぞれ「連続格納」と「分散格納」という二つの物理構造を表しています。 -実際、**物理構造はプログラムがメモリとキャッシュをどの程度効率的に利用するかを大きく決定し**、これがアルゴリズムの全体的なパフォーマンスに影響を与えます。 +実際には、**物理構造はプログラムにおけるメモリとキャッシュの利用効率を大きく左右し**、ひいてはアルゴリズムプログラム全体の性能に影響します。 -## コンピュータ記憶装置 +## コンピュータの記憶装置 -コンピュータには3種類の記憶装置があります:ハードディスクランダムアクセスメモリ(RAM)、およびキャッシュメモリです。以下の表は、コンピュータシステムにおけるそれぞれの役割とパフォーマンス特性を示しています。 +コンピュータには三種類の記憶装置があります。ハードディスク(hard disk)メモリ(random-access memory, RAM)キャッシュ(cache memory)です。以下の表は、これらがコンピュータシステムで担う役割と性能上の特徴を示しています。 -

  コンピュータ記憶装置

+

  コンピュータの記憶装置

-| | ハードディスク | メモリ | キャッシュ | -| ----------- | -------------------------------------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | -| 用途 | OS、プログラム、ファイルなどのデータの長期保存 | 現在実行中のプログラムと処理中のデータの一時保存 | 頻繁にアクセスされるデータと命令を保存し、CPUのメモリへのアクセス数を削減 | -| 揮発性 | 電源オフ後もデータは失われない | 電源オフ後にデータは失われる | 電源オフ後にデータは失われる | -| 容量 | より大きい、TBレベル | より小さい、GBレベル | 非常に小さい、MBレベル | -| 速度 | より遅い、数百から数千MB/s | より高速、数十GB/s | 非常に高速、数十から数百GB/s | -| 価格(USD) | より安価、数セント/GB | より高価、数ドル/GB | 非常に高価、CPUと一緒に価格設定 | +| | ハードディスク | メモリ | キャッシュ | +| -------------- | ---------------------------------------- | -------------------------------------- | ------------------------------------------------- | +| 用途 | OS、プログラム、ファイルなどを長期保存 | 実行中のプログラムや処理中のデータを一時保存 | 頻繁にアクセスされるデータや命令を保存し、CPU のメモリアクセス回数を減らす | +| 揮発性 | 電源断後もデータは失われない | 電源断後にデータは失われる | 電源断後にデータは失われる | +| 容量 | 大きい、TB 級 | 小さい、GB 級 | 非常に小さい、MB 級 | +| 速度 | 遅い、数百〜数千 MB/s | 速い、数十 GB/s | 非常に速い、数十〜数百 GB/s | +| 価格(人民元) | 比較的安価、数角〜数元 / GB | 比較的高価、数十〜数百元 / GB | 非常に高価、CPU と一体で価格設定される | -コンピュータ記憶システムは、下図に示すようにピラミッドとして視覚化できます。ピラミッドの上部にある記憶装置ほど高速で、容量が小さく、より高価です。このマルチレベル設計は偶然ではなく、コンピュータ科学者とエンジニアによる慎重な検討の結果です。 +コンピュータの記憶システムは、下図のようなピラミッド構造として捉えられます。ピラミッドの頂点に近い記憶装置ほど速度は速く、容量は小さく、コストは高くなります。この多層構造は偶然ではなく、コンピュータ科学者やエンジニアによる熟慮の末の設計です。 -- **ハードディスクをメモリに置き換えるのは困難です**。第一に、メモリ内のデータは電源オフ後に失われるため、長期データ保存には適していません。第二に、メモリはハードディスクよりも大幅に高価で、消費者市場での広範囲な使用の実現可能性を制限しています。 -- **キャッシュは大容量と高速のトレードオフに直面しています**。L1、L2、L3キャッシュの容量が増加するにつれて、その物理サイズが大きくなり、CPUコアからの距離が増加します。これによりデータ転送時間が長くなり、アクセス遅延が高くなります。現在の技術では、マルチレベルキャッシュ構造が容量、速度、コストの間の最適なバランスを提供します。 +- **ハードディスクはメモリで置き換えにくい**。まず、メモリ内のデータは電源断後に失われるため、長期保存には向きません。次に、メモリのコストはハードディスクの数十倍であり、消費者市場で広く普及しにくいという問題があります。 +- **キャッシュは大容量と高速性を両立しにくい**。L1、L2、L3 キャッシュの容量が段階的に増えるにつれて、物理サイズは大きくなり、CPU コアとの物理的距離も遠くなります。その結果、データ転送時間が増え、要素アクセスの遅延も大きくなります。現在の技術では、多層キャッシュ構造が容量、速度、コストの最適なバランスです。 -![コンピュータ記憶システム](ram_and_cache.assets/storage_pyramid.png) +![コンピュータの記憶システム](ram_and_cache.assets/storage_pyramid.png) !!! tip - コンピュータの記憶階層は、速度、容量、コストの間の慎重なバランスを反映しています。このタイプのトレードオフは様々な業界で一般的であり、利益と制限の間の最適なバランスを見つけることが重要です。 + コンピュータの記憶階層は、速度、容量、コストの三者間にある巧妙なバランスを体現しています。実際、このようなトレードオフはあらゆる工業分野に広く存在しており、異なる利点と制約のあいだで最適な均衡点を見つけることが求められます。 -全体的に、**ハードディスクは大量のデータの長期保存を提供し、メモリはプログラム実行中に処理されるデータの一時保存として機能し、キャッシュは頻繁にアクセスされるデータと命令を保存して実行効率を向上させます**。それらは一緒になってコンピュータシステムの効率的な動作を保証します。 +要するに、**ハードディスクは大量データの長期保存に、メモリはプログラム実行中に処理しているデータの一時保存に、キャッシュは頻繁にアクセスされるデータや命令の保存に用いられ**、プログラム実行効率を高めます。三者は協調して動作し、コンピュータシステムの高効率な運用を支えています。 -下図に示すように、プログラム実行中、データはハードディスクからメモリに読み込まれ、CPU計算が行われます。CPUの拡張として機能するキャッシュは、**メモリからインテリジェントにデータを先読み**し、CPUのより高速なデータアクセスを可能にします。これによりプログラム実行効率が大幅に向上し、低速なメモリへの依存が減少します。 +次の図に示すように、プログラム実行時にはデータがハードディスクからメモリへ読み込まれ、CPU の計算に使われます。キャッシュは CPU の一部と見なせ、**メモリからデータを賢く読み込むことで**、CPU に高速なデータ読み出しを提供し、プログラムの実行効率を大きく高め、低速なメモリへの依存を減らします。 -![ハードディスク、メモリ、キャッシュ間のデータフロー](ram_and_cache.assets/computer_storage_devices.png) +![ハードディスク、メモリ、キャッシュ間のデータの流れ](ram_and_cache.assets/computer_storage_devices.png) ## データ構造のメモリ効率 -メモリ空間利用の観点から、配列と連結リストにはそれぞれ利点と制限があります。 +メモリ空間の利用という観点では、配列と連結リストにはそれぞれ利点と制約があります。 -一方で、**メモリは限られており、複数のプログラム間で共有できない**ため、データ構造での空間使用の最適化は重要です。配列は要素が密接にパックされており、連結リストのように参照(ポインタ)のための追加メモリを必要としないため、空間効率的です。しかし、配列は連続したメモリブロックを事前に割り当てる必要があり、割り当てられた空間が実際の必要量を超える場合、無駄につながる可能性があります。配列の拡張も追加の時間と空間のオーバーヘッドを伴います。対照的に、連結リストは各ノードに対してメモリを動的に割り当て・解放し、ポインタのための追加メモリのコストでより大きな柔軟性を提供します。 +一方で、**メモリは有限であり、同じメモリ領域を複数のプログラムで共有することはできません**。そのため、データ構造にはできるだけ効率よく空間を使うことが求められます。配列の要素は密に並んでおり、連結リストのノード間参照(ポインタ)を保持する追加領域が不要なため、空間効率は高くなります。しかし、配列は十分な連続メモリを一度に確保する必要があり、メモリ浪費を招くことがありますし、拡張時にも追加の時間と空間コストがかかります。これに対して連結リストは「ノード」単位で動的にメモリを割り当て・解放でき、より高い柔軟性を備えています。 -一方で、プログラム実行中、**繰り返されるメモリの割り当てと解放はメモリの断片化を増加させ**、メモリ利用効率を低下させます。配列は連続記憶方式により、メモリ断片化を引き起こす可能性が比較的低いです。対照的に、連結リストは要素を非連続の場所に保存し、頻繁な挿入と削除はメモリ断片化を悪化させる可能性があります。 +他方で、プログラムの実行中には、**メモリの確保と解放を繰り返すにつれて、空きメモリの断片化はますます進み**、メモリ利用効率の低下を招きます。配列は連続した格納方式を取るため、比較的メモリ断片化を起こしにくい構造です。反対に、連結リストの要素は分散して格納されるため、頻繁な挿入や削除を行うと、より断片化を招きやすくなります。 ## データ構造のキャッシュ効率 -キャッシュはメモリよりも空間容量がはるかに小さいですが、はるかに高速で、プログラム実行速度において重要な役割を果たします。限られた容量のため、キャッシュは頻繁にアクセスされるデータのサブセットのみを保存できます。CPUがキャッシュに存在しないデータにアクセスしようとすると、キャッシュミスが発生し、CPUは低速なメモリから必要なデータを取得する必要があり、パフォーマンスに影響を与える可能性があります。 +キャッシュは容量こそメモリよりはるかに小さいものの、速度はメモリよりずっと速く、プログラム実行速度において極めて重要な役割を果たします。キャッシュ容量には限りがあり、頻繁にアクセスされる一部のデータしか保持できません。そのため、CPU がアクセスしようとするデータがキャッシュ内に存在しない場合、キャッシュミス(cache miss)が発生し、CPU は低速なメモリから必要なデータを読み込まなければなりません。 -明らかに、**キャッシュミスが少ないほど、CPUのデータ読み書き効率が高く**、プログラムパフォーマンスが向上します。CPUがキャッシュからデータを正常に取得する割合はキャッシュヒット率と呼ばれ、キャッシュ効率を測定するためによく使用される指標です。 +当然ながら、**「キャッシュミス」が少ないほど、CPU のデータ読み書き効率は高くなり**、プログラム性能も向上します。CPU がキャッシュからデータを正常に取得できた割合をキャッシュヒット率(cache hit rate)と呼び、この指標は通常、キャッシュ効率の評価に用いられます。 -より高い効率を達成するために、キャッシュは以下のデータロードメカニズムを採用します。 +できるだけ高い効率を実現するため、キャッシュは次のようなデータ読み込みの仕組みを採用しています。 -- **キャッシュライン**:キャッシュは個々のバイトではなく、キャッシュラインと呼ばれる単位でデータを保存・ロードして動作します。このアプローチは、一度により大きなデータブロックを転送することで効率を向上させます。 -- **先読みメカニズム**:プロセッサはデータアクセスパターン(例:連続または固定ストライドアクセス)を予測し、これらのパターンに基づいてデータをキャッシュに先読みして、キャッシュヒット率を向上させます。 -- **空間的局所性**:特定のデータがアクセスされると、近くのデータもまもなくアクセスされる可能性があります。これを活用するために、キャッシュは要求されたデータと一緒に隣接するデータをロードし、ヒット率を向上させます。 -- **時間的局所性**:データがアクセスされた場合、近い将来に再びアクセスされる可能性があります。キャッシュはこの原理を使用して、最近アクセスされたデータを保持してヒット率を向上させます。 +- **キャッシュライン**:キャッシュはデータを 1 バイト単位で保存・読み込みするのではなく、キャッシュライン単位で扱います。1 バイト単位の転送と比べて、キャッシュライン単位のほうが効率的です。 +- **プリフェッチ機構**:プロセッサはデータアクセスのパターン(たとえば順次アクセス、一定ステップ幅のスキップアクセスなど)を予測し、そのパターンに応じてデータをキャッシュへ読み込むことで、ヒット率を高めます。 +- **空間的局所性**:あるデータがアクセスされた場合、その近傍のデータも近いうちにアクセスされる可能性があります。そのため、キャッシュはあるデータを読み込む際に、その周辺のデータもあわせて読み込み、ヒット率を高めます。 +- **時間的局所性**:あるデータがアクセスされた場合、そのデータは近い将来に再びアクセスされる可能性が高いです。キャッシュはこの性質を利用し、最近アクセスしたデータを保持することでヒット率を高めます。 -実際、**配列と連結リストは異なるキャッシュ利用効率を持ち**、これは主に以下の側面に反映されます。 +実際には、**配列と連結リストではキャッシュの利用効率が異なり**、主に次の点に表れます。 -- **占有空間**:連結リスト要素は配列要素よりも多くの空間を占有するため、キャッシュに保持される有効データが少なくなります。 -- **キャッシュライン**:連結リストデータはメモリ全体に散在し、キャッシュは「行単位でロード」されるため、ロードされる無効データの割合が高くなります。 -- **先読みメカニズム**:配列のデータアクセスパターンは連結リストよりも「予測可能」で、つまりシステムがこれからロードされるデータを推測しやすいです。 -- **空間的局所性**:配列は連続したメモリ空間に保存されるため、ロードされているデータの近くのデータがまもなくアクセスされる可能性が高くなります。 +- **使用空間**:連結リストの要素は配列要素より多くの空間を占めるため、キャッシュに収まる有効データ量は少なくなります。 +- **キャッシュライン**:連結リストのデータはメモリの各所に分散しており、キャッシュは「ライン単位で読み込む」ため、無効データまで読み込む割合が高くなります。 +- **プリフェッチ機構**:配列のほうが連結リストよりもデータアクセスのパターンを「予測しやすく」、システムが次に読み込まれるデータを推測しやすくなります。 +- **空間的局所性**:配列はまとまったメモリ空間に格納されるため、読み込まれたデータの近くにあるデータも、まもなくアクセスされる可能性が高くなります。 -全体的に、**配列はより高いキャッシュヒット率を持ち、一般的に連結リストよりも操作効率が高いです**。これにより、配列に基づくデータ構造はアルゴリズム問題の解決において人気があります。 +全体として、**配列はより高いキャッシュヒット率を持つため、操作効率では通常、連結リストより優れています**。このため、アルゴリズム問題を解く際には、配列ベースで実装されたデータ構造のほうが好まれることが多くなります。 -**高いキャッシュ効率が配列が常に連結リストより優れているという意味ではない**ことに注意すべきです。データ構造の選択は特定のアプリケーション要件に依存すべきです。例えば、配列と連結リストの両方が「スタック」データ構造を実装できますが(次章で詳細説明)、それらは異なるシナリオに適しています。 +注意すべきなのは、**キャッシュ効率が高いからといって、配列があらゆる状況で連結リストより優れているとは限らない**という点です。実際にどのデータ構造を選ぶかは、具体的な要件に応じて決めるべきです。たとえば、配列と連結リストはいずれも「スタック」データ構造を実装できますが(次章で詳しく説明します)、適した場面は異なります。 -- アルゴリズム問題では、より高い操作効率とランダムアクセス機能を提供するため、配列に基づくスタックを選択する傾向があります。唯一のコストは配列に対して一定量のメモリ空間を事前に割り当てる必要があることです。 -- データ量が非常に大きく、高度に動的で、スタックの予想サイズを推定するのが困難な場合、連結リストに基づくスタックがより良い選択です。連結リストは大量のデータをメモリの異なる部分に分散でき、配列拡張の追加オーバーヘッドを回避できます。 +- アルゴリズム問題に取り組むときは、一般に配列ベースのスタックを選ぶ傾向があります。より高い操作効率とランダムアクセス能力を備えており、その代償は配列用に一定量のメモリを事前確保することだけです。 +- データ量が非常に大きく、動的性が高く、スタックの想定サイズを見積もりにくい場合は、連結リストベースのスタックのほうが適しています。連結リストなら大量のデータをメモリの異なる場所に分散して保存でき、配列拡張による追加コストも回避できます。 diff --git a/ja/docs/chapter_array_and_linkedlist/summary.md b/ja/docs/chapter_array_and_linkedlist/summary.md index 10f14eed6..b3200ea05 100644 --- a/ja/docs/chapter_array_and_linkedlist/summary.md +++ b/ja/docs/chapter_array_and_linkedlist/summary.md @@ -1,81 +1,86 @@ # まとめ -### 重要な復習 +### 要点の振り返り -- 配列と連結リストは2つの基本的なデータ構造であり、コンピュータメモリにおける2つの格納方法を表しています:連続空間格納と非連続空間格納です。それらの特性は互いに補完し合います。 -- 配列はランダムアクセスをサポートし、使用するメモリが少ない一方で、要素の挿入と削除は非効率的で、初期化後の長さが固定されています。 -- 連結リストは参照(ポインタ)の変更によって効率的なノードの挿入と削除を実装し、長さを柔軟に調整できますが、ノードアクセス効率が低く、より多くのメモリを消費します。 -- 連結リストの一般的な種類には、単方向連結リスト、循環連結リスト、双方向連結リストがあり、それぞれに独自の応用シナリオがあります。 -- リストは要素の順序付けられたコレクションで、追加、削除、変更をサポートし、通常は動的配列に基づいて実装され、配列の利点を保持しながら柔軟な長さ調整を可能にします。 -- リストの出現により配列の実用性が大幅に向上しましたが、一部のメモリ空間の無駄につながる可能性があります。 -- プログラム実行中、データは主にメモリに格納されます。配列はより高いメモリ空間効率を提供し、連結リストはメモリ使用においてより柔軟です。 -- キャッシュは、キャッシュライン、先読み、空間的局所性、時間的局所性などのメカニズムを通じてCPUに高速データアクセスを提供し、プログラム実行効率を大幅に向上させます。 -- より高いキャッシュヒット率により、配列は一般的に連結リストよりも効率的です。データ構造を選択する際は、特定のニーズとシナリオに基づいて適切な選択をすべきです。 +- 配列と連結リストは 2 種類の基本的なデータ構造であり、それぞれコンピュータメモリにおけるデータの 2 つの格納方式、すなわち連続領域への格納と分散領域への格納を表す。両者の特徴は相互補完的である。 +- 配列はランダムアクセスをサポートし、使用メモリも少ない。一方で、要素の挿入と削除の効率は低く、初期化後に長さを変更できない。 +- 連結リストは参照(ポインタ)を変更することでノードの挿入と削除を効率的に行え、長さも柔軟に調整できる。一方で、ノードへのアクセス効率は低く、メモリ使用量も多い。一般的な連結リストには単方向連結リスト、循環連結リスト、双方向連結リストがある。 +- リストは、追加・削除・検索・更新をサポートする順序付き要素集合であり、通常は動的配列に基づいて実装される。配列の利点を保ちながら、長さを柔軟に調整できる。 +- リストの登場により配列の実用性は大幅に高まったが、一部のメモリ領域が無駄になる可能性がある。 +- プログラムの実行時、データは主にメモリに格納される。配列はより高いメモリ空間効率を提供でき、連結リストはメモリ利用の面でより柔軟である。 +- キャッシュは、キャッシュライン、プリフェッチ機構、空間局所性と時間局所性といったデータ読み込み機構を通じて CPU に高速なデータアクセスを提供し、プログラムの実行効率を大きく向上させる。 +- 配列はキャッシュヒット率が高いため、通常は連結リストよりも高効率である。データ構造を選択する際は、具体的な要件や場面に応じて適切に選ぶべきである。 ### Q & A -**Q**:配列をスタックに格納するかヒープに格納するかは、時間と空間効率に影響しますか? +**Q**:配列をスタックに格納する場合とヒープに格納する場合では、時間効率と空間効率に影響がありますか? -スタックとヒープの両方に格納される配列は連続したメモリ空間に格納され、データ操作効率は本質的に同じです。しかし、スタックとヒープには独自の特性があり、以下の違いが生じます。 +スタック上とヒープ上の配列はいずれも連続したメモリ領域に格納されるため、データ操作の効率は基本的に同じである。ただし、スタックとヒープにはそれぞれ特徴があり、以下の違いが生じる。 -1. 割り当てと解放効率:スタックはより小さなメモリブロックで、コンパイラによって自動的に割り当てられます。ヒープメモリは比較的大きく、コードで動的に割り当てることができ、断片化しやすいです。したがって、ヒープでの割り当てと解放操作は一般的にスタックよりも遅くなります。 -2. サイズ制限:スタックメモリは比較的小さく、ヒープサイズは一般的に利用可能なメモリによって制限されます。したがって、ヒープは大きな配列の格納により適しています。 -3. 柔軟性:スタック上の配列のサイズはコンパイル時に決定される必要がありますが、ヒープ上の配列のサイズは実行時に動的に決定できます。 +1. 確保と解放の効率:スタックは比較的小さなメモリ領域で、確保はコンパイラによって自動的に行われる。一方、ヒープメモリは相対的に大きく、コード内で動的に確保できる反面、断片化しやすい。そのため、ヒープ上での確保と解放は通常スタック上より遅い。 +2. サイズ制限:スタックメモリは比較的小さく、ヒープのサイズは一般に利用可能メモリに制限される。そのため、ヒープは大きな配列の格納により適している。 +3. 柔軟性:スタック上の配列サイズはコンパイル時に確定している必要があるが、ヒープ上の配列サイズは実行時に動的に決定できる。 -**Q**:なぜ配列は同じ型の要素を必要とし、連結リストは同じ型の要素を強調しないのですか? +**Q**:なぜ配列では同じ型の要素が求められるのに、連結リストでは同じ型であることが強調されないのですか? -連結リストは参照(ポインタ)によって接続されたノードで構成され、各ノードはint、double、string、objectなど、異なる型のデータを格納できます。 +連結リストはノードで構成され、ノード同士は参照(ポインタ)で接続されている。各ノードには `int`、`double`、`string`、`object` など、異なる型のデータを格納できる。 -対照的に、配列要素は同じ型である必要があり、これにより対応する要素位置にアクセスするためのオフセットを計算できます。例えば、intとlong型の両方を含む配列で、単一要素がそれぞれ4バイトと8バイトを占有する場合、配列に2つの異なる長さの要素が含まれているため、以下の式を使用してオフセットを計算できません。 +これに対して、配列要素は同じ型でなければならない。そうでなければ、オフセットを計算して対応する要素位置を取得できないからである。たとえば、配列に `int` と `long` の 2 種類が同時に含まれていて、各要素がそれぞれ 4 バイトと 8 バイトを占める場合、配列内に 2 種類の「要素長」が存在するため、次の式ではオフセットを計算できない。 ```shell -# 要素メモリアドレス = 配列メモリアドレス + 要素長 * 要素インデックス +# 要素のメモリアドレス = 配列のメモリアドレス(先頭要素のメモリアドレス) + 要素長 * 要素インデックス ``` -**Q**:ノードを削除した後、`P.next`を`None`に設定する必要がありますか? +**Q**:ノード `P` を削除した後、`P.next` を `None` に設定する必要はありますか? -`P.next`を変更しなくても問題ありません。連結リストの観点から、ヘッドノードからテールノードまでの巡回で`P`に遭遇することはもうありません。これは、ノード`P`がリストから効果的に削除されたことを意味し、`P`が指す場所はもはやリストに影響しません。 +`P.next` を変更しなくてもよい。この連結リストの観点では、先頭ノードから末尾ノードまでたどっても、もはや `P` に出会うことはない。つまり、ノード `P` はすでに連結リストから削除されており、この時点で `P` がどこを指していても、この連結リストには影響しない。 -ガベージコレクションの観点から、Java、Python、Goなどの自動ガベージコレクションメカニズムを持つ言語では、ノード`P`が収集されるかどうかは、それを指す参照がまだあるかどうかに依存し、`P.next`の値には依存しません。CやC++などの言語では、ノードのメモリを手動で解放する必要があります。 +データ構造とアルゴリズム(問題を解くとき)の観点では、切り離さなくても問題はなく、プログラムのロジックが正しいことを保証すればよい。標準ライブラリの観点では、切り離したほうがより安全で、ロジックも明確である。切り離さない場合、削除されたノードが適切に回収されなかったとすると、後続ノードのメモリ回収に影響する可能性がある。 -**Q**:連結リストでは、挿入と削除操作の時間計算量は`O(1)`です。しかし、挿入や削除前の要素検索には`O(n)`時間がかかるので、なぜ時間計算量は`O(n)`ではないのですか? +**Q**:連結リストでの挿入と削除の時間計算量は $O(1)$ です。しかし、追加や削除の前には要素を探すのに $O(n)$ の時間が必要です。では、なぜ時間計算量は $O(n)$ ではないのですか? -要素を最初に検索してから削除する場合、時間計算量は確かに`O(n)`です。しかし、連結リストの挿入と削除における`O(1)`の利点は他のアプリケーションで実現できます。例えば、連結リストを使用した両端キューの実装では、常にヘッドとテールノードを指すポインタを維持し、各挿入と削除操作を`O(1)`にします。 +要素を先に探してから削除するのであれば、時間計算量が $O(n)$ であるのは確かである。しかし、連結リストの $O(1)$ での追加・削除という利点は、ほかの用途で生かせる。たとえば、両端キューは連結リストで実装するのに適しており、先頭ノードと末尾ノードを常に指すポインタ変数を維持すれば、各挿入・削除操作はどれも $O(1)$ になる。 -**Q**:「連結リストの定義と格納方法」の図で、薄青色の格納ノードは単一のメモリアドレスを占有しますか、それともノード値と半分を共有しますか? +**Q**:図「連結リストの定義と格納方式」で、薄青色のノードポインタ部分は 1 つのメモリアドレスを占めているのですか? それともノード値と半分ずつなのでしょうか? -図は単なる定性的な表現であり、定量的分析は特定の状況に依存します。 +この模式図は定性的な表現にすぎず、定量的な表現は具体的な状況に応じて分析する必要がある。 -- 異なる型のノード値は異なる量の空間を占有します。例えば、int、long、double、オブジェクトインスタンスです。 -- ポインタ変数によって占有されるメモリ空間は、使用されるオペレーティングシステムとコンパイル環境に依存し、通常8バイトまたは4バイトです。 +- ノード値が占める領域は型によって異なり、たとえば `int`、`long`、`double`、インスタンスオブジェクトなどがある。 +- ポインタ変数が占めるメモリ空間の大きさは、使用する OS やコンパイル環境によって異なり、多くは 8 バイトまたは 4 バイトである。 -**Q**:リストの末尾への要素追加は常に`O(1)`ですか? +**Q**:リストの末尾への要素追加は常に $O(1)$ ですか? -要素を追加することでリスト長を超える場合、リストは最初に拡張される必要があります。システムは新しいメモリブロックを要求し、元のリストのすべての要素を移動するため、この場合の時間計算量は`O(n)`になります。 +要素を追加する際にリスト長を超える場合は、先にリストを拡張してから追加する必要がある。システムは新しいメモリ領域を確保し、元のリストの全要素をそこへ移動するため、このとき時間計算量は $O(n)$ になる。 -**Q**:「リストの出現により配列の実用性が大幅に向上しましたが、一部のメモリ空間の無駄につながる可能性があります」という文は、容量、長さ、拡張係数などの追加変数によって占有されるメモリを指していますか? +**Q**:「リストの登場により配列の実用性は大きく向上したが、一部のメモリ空間が無駄になる可能性がある」というのは、容量、長さ、拡張倍率のような追加変数が占めるメモリのことですか? -ここでの空間の無駄は主に2つの側面を指します:一方で、リストは初期長で設定されますが、常に必要とは限りません。他方で、頻繁な拡張を防ぐため、拡張は通常$\times 1.5$などの係数で乗算されます。これにより多くの空きスロットが生まれ、通常は完全に埋めることができません。 +ここでいう空間の無駄には主に 2 つの意味がある。一方では、リストには初期長が設定されるが、必ずしもそれだけ必要とは限らない。もう一方では、頻繁な拡張を防ぐため、拡張時には通常ある係数、たとえば $\times 1.5$ を掛ける。このため、多くの空きスロットが生じ、通常それらを完全に埋めることはできない。 -**Q**:Pythonで`n = [1, 2, 3]`を初期化した後、これら3つの要素のアドレスは連続していますが、`m = [2, 1, 3]`を初期化すると、各要素の`id`は連続していないが`n`のものと同一です。これらの要素のアドレスが連続していない場合、`m`はまだ配列ですか? +**Q**:Python で `n = [1, 2, 3]` を初期化した後、この 3 つの要素のアドレスは連続しています。しかし `m = [2, 1, 3]` を初期化すると、各要素の id は連続しておらず、それぞれ `n` 内の同じ値と一致していることがわかります。これらの要素のアドレスが連続していないなら、`m` も配列なのですか? -リスト要素を連結リストノード`n = [n1, n2, n3, n4, n5]`に置き換える場合、これら5つのノードオブジェクトも通常メモリ全体に分散しています。しかし、リストインデックスが与えられれば、`O(1)`時間でノードのメモリアドレスにアクセスでき、対応するノードにアクセスできます。これは、配列がノード自体ではなく、ノードへの参照を格納するためです。 +仮にリスト要素を連結リストのノード `n = [n1, n2, n3, n4, n5]` に置き換えたとしても、通常この 5 つのノードオブジェクトもメモリ上の各所に分散して格納される。それでも、与えられたリストインデックスに対して、私たちは依然として $O(1)$ 時間でノードのメモリアドレスを取得し、対応するノードにアクセスできる。これは、配列に格納されているのがノードそのものではなく、ノードへの参照だからである。 -多くの言語とは異なり、Pythonでは数値もオブジェクトとしてラップされ、リストは数値自体ではなく、これらの数値への参照を格納します。したがって、2つの配列の同じ数値が同じ`id`を持ち、これらの数値のメモリアドレスは連続である必要がないことがわかります。 +多くの言語と異なり、Python では数値もオブジェクトとしてラップされており、リストに格納されているのは数値そのものではなく、数値への参照である。そのため、2 つの配列内の同じ数値が同一の id を持つことがあり、しかもそれらの数値のメモリアドレスは連続している必要がない。 -**Q**:C++ STLの`std::list`はすでに双方向連結リストを実装していますが、一部のアルゴリズム書籍では直接使用していないようです。何か制限がありますか? +**Q**:C++ STL の `std::list` はすでに双方向連結リストを実装していますが、アルゴリズム本ではあまり直接使われないようです。何か制約があるのでしょうか? -一方で、アルゴリズムを実装する際は配列を使用することを好み、必要な場合のみ連結リストを使用します。主に2つの理由があります。 +一方では、私たちは多くの場合、アルゴリズムの実装に配列を好み、必要なときにだけ連結リストを使う。その主な理由は 2 つある。 -- 空間オーバーヘッド:各要素に2つの追加ポインタ(前の要素用と次の要素用)が必要なため、`std::list`は通常`std::vector`よりも多くの空間を占有します。 -- キャッシュ非友好的:データが連続して格納されていないため、`std::list`はキャッシュ利用率が低くなります。一般的に、`std::vector`の方がパフォーマンスが優れています。 +- 空間オーバーヘッド:各要素には 2 つの追加ポインタ(前の要素用と次の要素用)が必要なため、`std::list` は通常 `std::vector` より多くの空間を消費する。 +- キャッシュ非効率:データが連続して格納されていないため、`std::list` はキャッシュの利用効率が低い。一般には、`std::vector` のほうが性能がよい。 -他方で、連結リストは主に二分木とグラフに必要です。スタックとキューは、連結リストではなく、プログラミング言語の`stack`と`queue`クラスを使用して実装されることが多いです。 +もう一方では、連結リストを使う必要がある代表的な場面は主に二分木とグラフである。スタックやキューについては、連結リストではなく、たいてい言語が提供する `stack` と `queue` を使う。 -**Q**:リスト`res = [0] * self.size()`を初期化すると、`res`の各要素は同じアドレスを参照しますか? +**Q**:`res = [[0]] * n` という操作で 2 次元リストを生成した場合、それぞれの `[0]` は独立していますか? -いいえ。しかし、この問題は二次元配列で発生します。例えば、二次元リスト`res = [[0]] * self.size()`を初期化すると、同じリスト`[0]`を複数回参照することになります。 +独立していない。この 2 次元リストでは、すべての `[0]` は実際には同一オブジェクトへの参照である。そのうちの 1 つを変更すると、対応するすべての要素が一緒に変化することがわかる。 -**Q**:ノードを削除する際、その後続ノードへの参照を断つ必要がありますか? +2 次元リスト内の各 `[0]` を独立させたい場合は、`res = [[0] for _ in range(n)]` を使って実現できる。この方式の原理は、独立した `[0]` リストオブジェクトを $n$ 個初期化していることにある。 -データ構造とアルゴリズム(問題解決)の観点から、プログラムのロジックが正しい限り、リンクを断たなくても問題ありません。標準ライブラリの観点から、リンクを断つ方が安全で論理的に明確です。リンクを断たず、削除されたノードが適切にリサイクルされない場合、後続ノードのメモリのリサイクルに影響を与える可能性があります。 +**Q**:`res = [0] * n` という操作で生成されたリストでは、それぞれの整数 0 は独立していますか? + +このリストでは、すべての整数 0 が同一オブジェクトへの参照である。これは、Python が小さな整数(通常は -5 から 256)に対してキャッシュプール機構を採用し、オブジェクトの再利用を最大化して性能を向上させているためである。 + +それらは同じオブジェクトを指しているが、それでもリスト内の各要素は独立して変更できる。これは、Python の整数が「イミュータブルオブジェクト」だからである。ある要素を変更するとき、実際には別のオブジェクトへの参照に切り替わるのであって、元のオブジェクトそのものを変更しているわけではない。 + +しかし、リスト要素が「ミュータブルオブジェクト」(たとえばリスト、辞書、クラスインスタンスなど)である場合は、ある要素を変更するとそのオブジェクト自体が直接変更され、そのオブジェクトを参照しているすべての要素に同じ変化が生じる。 diff --git a/ja/docs/chapter_backtracking/backtracking_algorithm.md b/ja/docs/chapter_backtracking/backtracking_algorithm.md index 846071cc6..564277b5b 100644 --- a/ja/docs/chapter_backtracking/backtracking_algorithm.md +++ b/ja/docs/chapter_backtracking/backtracking_algorithm.md @@ -1,45 +1,45 @@ # バックトラッキングアルゴリズム -バックトラッキングアルゴリズムは全数探索によって問題を解決する方法です。その核心概念は、初期状態から開始してすべての可能な解を総当たりで探索することです。アルゴリズムは正しいものを記録し、解が見つかるか、すべての可能な解が試されたが解が見つからないまで続けます。 +バックトラッキングアルゴリズム(backtracking algorithm)は、総当たりによって問題を解く手法です。その中核となる考え方は、初期状態から出発し、あり得るすべての解を力任せに探索し、正しい解に到達したらそれを記録し、解を見つけるか、考えられるすべての選択を試しても解が見つからなくなるまで続ける、というものです。 -バックトラッキングは通常「深さ優先探索」を使用して解空間を走査します。「二分木」の章で、前順、中順、後順走査はすべて深さ優先探索であることを述べました。次に、前順走査を使用してバックトラッキング問題を解決し、アルゴリズムの動作を段階的に理解していきます。 +バックトラッキングアルゴリズムでは、通常「深さ優先探索」を用いて解空間をたどります。「二分木」の章で述べたように、前順・中順・後順走査はいずれも深さ優先探索に属します。ここでは前順走査を使ってバックトラッキング問題を構成し、その仕組みを段階的に理解していきます。 -!!! question "例1" +!!! question "例題1" - 二分木が与えられた場合、値が $7$ のすべてのノードを検索して記録し、リストで返してください。 + 1 本の二分木が与えられたとき、値が $7$ のノードをすべて探索して記録し、そのノードのリストを返してください。 -この問題を解決するために、この木を前順で走査し、現在のノードの値が $7$ かどうかを確認します。そうであれば、ノードの値を結果リスト `res` に追加します。プロセスは以下の図に示されています: +この問題では、この木を前順走査し、現在のノードの値が $7$ かどうかを判定します。該当する場合は、そのノードの値を結果リスト `res` に追加します。関連する処理は下図と次のコードのとおりです。 ```src [file]{preorder_traversal_i_compact}-[class]{}-[func]{pre_order} ``` -![前順走査でのノード検索](backtracking_algorithm.assets/preorder_find_nodes.png) +![前順走査でノードを探索する](backtracking_algorithm.assets/preorder_find_nodes.png) -## 試行と後退 +## 試行と戻る -**解空間を探索する際に「試行」と「後退」戦略を使用するため、バックトラッキングアルゴリズムと呼ばれます**。探索中、満足のいく解を得るためにもはや進めない状態に遭遇するたびに、前の選択を取り消して前の状態に戻り、次の試行のために他の可能な選択を選択できるようにします。 +**バックトラッキングアルゴリズムと呼ばれるのは、解空間を探索する際に「試行」と「戻る」という戦略を取るためです**。探索中に、ある状態から先へ進めない、または条件を満たす解を得られないと分かった場合、アルゴリズムは直前の選択を取り消して前の状態へ戻り、別の選択肢を試します。 -例1では、各ノードの訪問が「試行」を開始します。そして葉ノードを通過するか、`return` 文で親ノードに戻ることが「後退」を示唆します。 +例題1では、各ノードへの訪問が 1 回の「試行」に対応し、葉ノードを越えるか親ノードへ戻る `return` は「戻る」を表します。 -**後退は単に関数の戻り値ではないことに注意してください**。例1の問題を少し拡張して、それが何を意味するかを説明します。 +ここで強調しておきたいのは、**戻るとは関数の return だけを指すわけではない**という点です。これを説明するために、例題1を少し拡張します。 -!!! question "例2" +!!! question "例題2" - 二分木で、値が $7$ のすべてのノードを検索し、すべてのマッチングノードについて、**ルートノードからそのノードまでのパスを返してください**。 + 二分木の中で値が $7$ のノードをすべて探索し、**根ノードからそれらのノードまでの経路を返してください**。 -例1のコードに基づいて、訪問したノードパスを記録するために `path` というリストを使用する必要があります。値が $7$ のノードに到達すると、`path` をコピーして結果リスト `res` に追加します。走査後、`res` にはすべての解が保持されます。コードは以下の通りです: +例題1のコードを土台に、訪問済みノードの経路を記録するためのリスト `path` を導入します。値が $7$ のノードに到達したら、`path` をコピーして結果リスト `res` に追加します。走査が完了すると、`res` にはすべての解が保存されています。コードは次のとおりです。 ```src [file]{preorder_traversal_ii_compact}-[class]{}-[func]{pre_order} ``` -各「試行」で、現在のノードを `path` に追加することでパスを記録します。「後退」が必要なときはいつでも、`path` からノードをポップして**この失敗した試行前の状態を復元します**。 +各「試行」で現在のノードを `path` に追加して経路を記録し、「戻る」前にはそのノードを `path` から取り除き、**今回の試行前の状態を復元する**必要があります。 -以下の図に示すプロセスを観察することで、**試行は「前進」のようで、後退は「元に戻す」のようです**。後者のペアは、対応するものに対する逆操作と見なすことができます。 +次の図に示す過程を見ると、**試行と戻るは「前進」と「取り消し」として理解できます**。この 2 つの操作は互いに逆向きです。 === "<1>" - ![試行と後退](backtracking_algorithm.assets/preorder_find_paths_step1.png) + ![試行と戻る](backtracking_algorithm.assets/preorder_find_paths_step1.png) === "<2>" ![preorder_find_paths_step2](backtracking_algorithm.assets/preorder_find_paths_step2.png) @@ -71,72 +71,72 @@ === "<11>" ![preorder_find_paths_step11](backtracking_algorithm.assets/preorder_find_paths_step11.png) -## 剪定 +## 枝刈り -複雑なバックトラッキング問題は通常1つ以上の制約を含み、**これらは「剪定」によく使用されます**。 +複雑なバックトラッキング問題には、通常 1 つ以上の制約条件が含まれます。**制約条件は多くの場合「枝刈り」に利用できます**。 -!!! question "例3" +!!! question "例題3" - 二分木で、値が $7$ のすべてのノードを検索し、ルートからこれらのノードまでのパスを返してください。**ただし、パスには値が $3$ のノードを含まないという制限があります**。 + 二分木の中で値が $7$ のノードをすべて探索し、根ノードからそれらのノードまでの経路を返してください。**ただし、経路には値が $3$ のノードを含めてはいけません**。 -上記の制約を満たすために、**剪定操作を追加する必要があります**:検索プロセス中に、値が $3$ のノードに遭遇した場合、そのパスを通じてさらに検索することを即座に中止します。コードは以下の通りです: +上の制約条件を満たすために、**枝刈り操作を追加する必要があります**。探索中に値が $3$ のノードに出会った場合は、そこで早めに return し、それ以上探索を続けません。コードは次のとおりです。 ```src [file]{preorder_traversal_iii_compact}-[class]{}-[func]{pre_order} ``` -「剪定」は非常に生き生きとした名詞です。以下の図に示すように、検索プロセスで、**制約を満たさない検索分岐を「切り取り」ます**。さらなる不要な試行を避け、検索効率を向上させます。 +「枝刈り」は非常にイメージしやすい名称です。次の図のように、探索中に**制約条件を満たさない探索分岐を切り落とす**ことで、多くの無意味な試行を避け、探索効率を高められます。 -![制約に基づく剪定](backtracking_algorithm.assets/preorder_find_constrained_paths.png) +![制約条件にもとづく枝刈り](backtracking_algorithm.assets/preorder_find_constrained_paths.png) ## フレームワークコード -今度は、バックトラッキングから「試行、後退、剪定」の主要なフレームワークを抽出して、コードの汎用性を向上させてみましょう。 +次に、バックトラッキングにおける「試行・戻る・枝刈り」の本体部分を抽出し、汎用性の高いコードフレームワークへまとめてみます。 -以下のフレームワークコードでは、`state` は問題の現在の状態を表し、`choices` は現在の状態で利用可能な選択肢を表します: +以下のフレームワークコードでは、`state` は問題の現在状態、`choices` はその状態で取り得る選択肢を表します。 === "Python" ```python title="" def backtrack(state: State, choices: list[choice], res: list[state]): - """バックトラッキングアルゴリズムフレームワーク""" - # 解かどうかを確認 + """バックトラッキングアルゴリズムのフレームワーク""" + # 解かどうかを判定 if is_solution(state): # 解を記録 record_solution(state, res) - # 検索を停止 + # これ以上探索しない return - # すべての選択肢を反復 + # すべての選択肢を走査 for choice in choices: - # 剪定:選択肢が有効かどうかを確認 + # 枝刈り: 選択が妥当かを判定 if is_valid(state, choice): - # 試行:選択を行い、状態を更新 + # 試行: 選択を行い、状態を更新 make_choice(state, choice) backtrack(state, choices, res) - # 後退:選択を取り消し、前の状態に戻す + # 戻る: 選択を取り消し、前の状態に戻す undo_choice(state, choice) ``` === "C++" ```cpp title="" - /* バックトラッキングアルゴリズムフレームワーク */ + /* バックトラッキングアルゴリズムのフレームワーク */ void backtrack(State *state, vector &choices, vector &res) { - // 解かどうかを確認 + // 解かどうかを判定 if (isSolution(state)) { // 解を記録 recordSolution(state, res); - // 検索を停止 + // これ以上探索しない return; } - // すべての選択肢を反復 + // すべての選択肢を走査 for (Choice choice : choices) { - // 剪定:選択肢が有効かどうかを確認 + // 枝刈り: 選択が妥当かを判定 if (isValid(state, choice)) { - // 試行:選択を行い、状態を更新 + // 試行: 選択を行い、状態を更新 makeChoice(state, choice); backtrack(state, choices, res); - // 後退:選択を取り消し、前の状態に戻す + // 戻る: 選択を取り消し、前の状態に戻す undoChoice(state, choice); } } @@ -146,23 +146,23 @@ === "Java" ```java title="" - /* バックトラッキングアルゴリズムフレームワーク */ + /* バックトラッキングアルゴリズムのフレームワーク */ void backtrack(State state, List choices, List res) { - // 解かどうかを確認 + // 解かどうかを判定 if (isSolution(state)) { // 解を記録 recordSolution(state, res); - // 検索を停止 + // これ以上探索しない return; } - // すべての選択肢を反復 + // すべての選択肢を走査 for (Choice choice : choices) { - // 剪定:選択肢が有効かどうかを確認 + // 枝刈り: 選択が妥当かを判定 if (isValid(state, choice)) { - // 試行:選択を行い、状態を更新 + // 試行: 選択を行い、状態を更新 makeChoice(state, choice); backtrack(state, choices, res); - // 後退:選択を取り消し、前の状態に戻す + // 戻る: 選択を取り消し、前の状態に戻す undoChoice(state, choice); } } @@ -172,23 +172,23 @@ === "C#" ```csharp title="" - /* バックトラッキングアルゴリズムフレームワーク */ + /* バックトラッキングアルゴリズムのフレームワーク */ void Backtrack(State state, List choices, List res) { - // 解かどうかを確認 + // 解かどうかを判定 if (IsSolution(state)) { // 解を記録 RecordSolution(state, res); - // 検索を停止 + // これ以上探索しない return; } - // すべての選択肢を反復 + // すべての選択肢を走査 foreach (Choice choice in choices) { - // 剪定:選択肢が有効かどうかを確認 + // 枝刈り: 選択が妥当かを判定 if (IsValid(state, choice)) { - // 試行:選択を行い、状態を更新 + // 試行: 選択を行い、状態を更新 MakeChoice(state, choice); Backtrack(state, choices, res); - // 後退:選択を取り消し、前の状態に戻す + // 戻る: 選択を取り消し、前の状態に戻す UndoChoice(state, choice); } } @@ -198,23 +198,23 @@ === "Go" ```go title="" - /* バックトラッキングアルゴリズムフレームワーク */ + /* バックトラッキングアルゴリズムのフレームワーク */ func backtrack(state *State, choices []Choice, res *[]State) { - // 解かどうかを確認 + // 解かどうかを判定 if isSolution(state) { // 解を記録 recordSolution(state, res) - // 検索を停止 + // これ以上探索しない return } - // すべての選択肢を反復 + // すべての選択肢を走査 for _, choice := range choices { - // 剪定:選択肢が有効かどうかを確認 + // 枝刈り: 選択が妥当かを判定 if isValid(state, choice) { - // 試行:選択を行い、状態を更新 + // 試行: 選択を行い、状態を更新 makeChoice(state, choice) backtrack(state, choices, res) - // 後退:選択を取り消し、前の状態に戻す + // 戻る: 選択を取り消し、前の状態に戻す undoChoice(state, choice) } } @@ -224,23 +224,23 @@ === "Swift" ```swift title="" - /* バックトラッキングアルゴリズムフレームワーク */ + /* バックトラッキングアルゴリズムのフレームワーク */ func backtrack(state: inout State, choices: [Choice], res: inout [State]) { - // 解かどうかを確認 + // 解かどうかを判定 if isSolution(state: state) { // 解を記録 recordSolution(state: state, res: &res) - // 検索を停止 + // これ以上探索しない return } - // すべての選択肢を反復 + // すべての選択肢を走査 for choice in choices { - // 剪定:選択肢が有効かどうかを確認 + // 枝刈り: 選択が妥当かを判定 if isValid(state: state, choice: choice) { - // 試行:選択を行い、状態を更新 + // 試行: 選択を行い、状態を更新 makeChoice(state: &state, choice: choice) backtrack(state: &state, choices: choices, res: &res) - // 後退:選択を取り消し、前の状態に戻す + // 戻る: 選択を取り消し、前の状態に戻す undoChoice(state: &state, choice: choice) } } @@ -250,23 +250,23 @@ === "JS" ```javascript title="" - /* バックトラッキングアルゴリズムフレームワーク */ + /* バックトラッキングアルゴリズムのフレームワーク */ function backtrack(state, choices, res) { - // 解かどうかを確認 + // 解かどうかを判定 if (isSolution(state)) { // 解を記録 recordSolution(state, res); - // 検索を停止 + // これ以上探索しない return; } - // すべての選択肢を反復 + // すべての選択肢を走査 for (let choice of choices) { - // 剪定:選択肢が有効かどうかを確認 + // 枝刈り: 選択が妥当かを判定 if (isValid(state, choice)) { - // 試行:選択を行い、状態を更新 + // 試行: 選択を行い、状態を更新 makeChoice(state, choice); backtrack(state, choices, res); - // 後退:選択を取り消し、前の状態に戻す + // 戻る: 選択を取り消し、前の状態に戻す undoChoice(state, choice); } } @@ -276,23 +276,23 @@ === "TS" ```typescript title="" - /* バックトラッキングアルゴリズムフレームワーク */ + /* バックトラッキングアルゴリズムのフレームワーク */ function backtrack(state: State, choices: Choice[], res: State[]): void { - // 解かどうかを確認 + // 解かどうかを判定 if (isSolution(state)) { // 解を記録 recordSolution(state, res); - // 検索を停止 + // これ以上探索しない return; } - // すべての選択肢を反復 + // すべての選択肢を走査 for (let choice of choices) { - // 剪定:選択肢が有効かどうかを確認 + // 枝刈り: 選択が妥当かを判定 if (isValid(state, choice)) { - // 試行:選択を行い、状態を更新 + // 試行: 選択を行い、状態を更新 makeChoice(state, choice); backtrack(state, choices, res); - // 後退:選択を取り消し、前の状態に戻す + // 戻る: 選択を取り消し、前の状態に戻す undoChoice(state, choice); } } @@ -302,23 +302,23 @@ === "Dart" ```dart title="" - /* バックトラッキングアルゴリズムフレームワーク */ + /* バックトラッキングアルゴリズムのフレームワーク */ void backtrack(State state, List, List res) { - // 解かどうかを確認 + // 解かどうかを判定 if (isSolution(state)) { // 解を記録 recordSolution(state, res); - // 検索を停止 + // これ以上探索しない return; } - // すべての選択肢を反復 + // すべての選択肢を走査 for (Choice choice in choices) { - // 剪定:選択肢が有効かどうかを確認 + // 枝刈り: 選択が妥当かを判定 if (isValid(state, choice)) { - // 試行:選択を行い、状態を更新 + // 試行: 選択を行い、状態を更新 makeChoice(state, choice); backtrack(state, choices, res); - // 後退:選択を取り消し、前の状態に戻す + // 戻る: 選択を取り消し、前の状態に戻す undoChoice(state, choice); } } @@ -328,23 +328,23 @@ === "Rust" ```rust title="" - /* バックトラッキングアルゴリズムフレームワーク */ + /* バックトラッキングアルゴリズムのフレームワーク */ fn backtrack(state: &mut State, choices: &Vec, res: &mut Vec) { - // 解かどうかを確認 + // 解かどうかを判定 if is_solution(state) { // 解を記録 record_solution(state, res); - // 検索を停止 + // これ以上探索しない return; } - // すべての選択肢を反復 + // すべての選択肢を走査 for choice in choices { - // 剪定:選択肢が有効かどうかを確認 + // 枝刈り: 選択が妥当かを判定 if is_valid(state, choice) { - // 試行:選択を行い、状態を更新 + // 試行: 選択を行い、状態を更新 make_choice(state, choice); backtrack(state, choices, res); - // 後退:選択を取り消し、前の状態に戻す + // 戻る: 選択を取り消し、前の状態に戻す undo_choice(state, choice); } } @@ -354,23 +354,23 @@ === "C" ```c title="" - /* バックトラッキングアルゴリズムフレームワーク */ + /* バックトラッキングアルゴリズムのフレームワーク */ void backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) { - // 解かどうかを確認 + // 解かどうかを判定 if (isSolution(state)) { // 解を記録 recordSolution(state, res, numRes); - // 検索を停止 + // これ以上探索しない return; } - // すべての選択肢を反復 + // すべての選択肢を走査 for (int i = 0; i < numChoices; i++) { - // 剪定:選択肢が有効かどうかを確認 + // 枝刈り: 選択が妥当かを判定 if (isValid(state, &choices[i])) { - // 試行:選択を行い、状態を更新 + // 試行: 選択を行い、状態を更新 makeChoice(state, &choices[i]); backtrack(state, choices, numChoices, res, numRes); - // 後退:選択を取り消し、前の状態に戻す + // 戻る: 選択を取り消し、前の状態に戻す undoChoice(state, &choices[i]); } } @@ -380,23 +380,23 @@ === "Kotlin" ```kotlin title="" - /* バックトラッキングアルゴリズムフレームワーク */ + /* バックトラッキングアルゴリズムのフレームワーク */ fun backtrack(state: State?, choices: List, res: List?) { - // 解かどうかを確認 + // 解かどうかを判定 if (isSolution(state)) { // 解を記録 recordSolution(state, res) - // 検索を停止 + // これ以上探索しない return } - // すべての選択肢を反復 + // すべての選択肢を走査 for (choice in choices) { - // 剪定:選択肢が有効かどうかを確認 + // 枝刈り: 選択が妥当かを判定 if (isValid(state, choice)) { - // 試行:選択を行い、状態を更新 + // 試行: 選択を行い、状態を更新 makeChoice(state, choice) backtrack(state, choices, res) - // 後退:選択を取り消し、前の状態に戻す + // 戻る: 選択を取り消し、前の状態に戻す undoChoice(state, choice) } } @@ -406,98 +406,98 @@ === "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 ``` -次に、フレームワークコードに基づいて例題 3 を解きます。状態 `state` はノードの走査経路を表し、選択肢 `choices` は現在ノードの左子ノードと右子ノード、結果 `res` は経路リストです: +次に、このフレームワークコードを用いて例題3を解きます。状態 `state` はノードの走査経路、選択肢 `choices` は現在のノードの左子ノードと右子ノード、結果 `res` は経路のリストです。 ```src [file]{preorder_traversal_iii_template}-[class]{}-[func]{backtrack} ``` -問題文の意味に従い、値が $7$ のノードを見つけた後も探索を続ける必要があります。**したがって、解を記録した後の `return` 文を削除する必要があります**。次の図は、`return` 文を保持する場合と削除する場合の探索過程の比較です。 +問題の条件より、値が $7$ のノードを見つけた後も探索を続ける必要があります。**そのため、解を記録した後の `return` 文は削除しなければなりません**。次の図は、`return` 文を残す場合と削除する場合の探索過程を比較したものです。 -![returnを保持する場合と削除する場合の探索過程の比較](backtracking_algorithm.assets/backtrack_remove_return_or_not.png) +![return を残す場合と削除する場合の探索過程の比較](backtracking_algorithm.assets/backtrack_remove_return_or_not.png) -前順走査に基づくコード実装と比べると、バックトラッキングアルゴリズムのフレームワークに基づく実装はやや冗長に見えますが、汎用性はより高いです。実際、**多くのバックトラッキング問題はこのフレームワークの下で解くことができます**。具体的な問題に応じて `state` と `choices` を定義し、フレームワーク内の各メソッドを実装すればよいのです。 +前順走査にもとづく実装と比べると、バックトラッキングアルゴリズムのフレームワークにもとづく実装はやや冗長に見えますが、汎用性に優れています。実際、**多くのバックトラッキング問題はこのフレームワークで解けます**。具体的な問題に応じて `state` と `choices` を定義し、各メソッドを実装すれば十分です。 ## よく使われる用語 -アルゴリズム問題をより明確に分析するために、バックトラッキングアルゴリズムでよく使われる用語の意味をまとめ、例題 3 の対応例を以下の表に示します。 +アルゴリズム問題をより明確に分析するために、バックトラッキングでよく使われる用語の意味を整理し、例題3に対応する例を次の表にまとめます。 -

  バックトラッキングアルゴリズムでよく使われる用語

+

  よく使われるバックトラッキング用語

-| 名称 | 定義 | 例題 3 | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- | -| 解(solution) | 解は問題の特定条件を満たす答えであり、1 つまたは複数存在する可能性がある | 根ノードからノード $7$ までの制約条件を満たすすべての経路 | -| 制約条件(constraint) | 制約条件は、解の実現可能性を制限する条件であり、通常は枝刈りに使用される | 経路にノード $3$ を含まない | -| 状態(state) | 状態は、ある時点での問題の状況を表し、これまでに行った選択を含む | 現在訪問したノード経路、すなわち `path` ノードリスト | -| 試行(attempt) | 試行は、利用可能な選択肢に基づいて解空間を探索する過程であり、選択を行い、状態を更新し、解かどうかを確認する | 左(右)子ノードを再帰的に訪問し、ノードを `path` に追加し、ノードの値が $7$ かを確認する | -| バックトラック(backtracking) | 制約条件を満たさない状態に遭遇した場合、以前の選択を取り消して前の状態に戻ること | 葉ノードを越えたとき、探索終了、値が $3$ のノードに遭遇したとき探索を終了し、関数が戻る | -| 枝刈り(pruning) | 問題の特性や制約条件に基づき、無意味な探索経路を避ける方法であり、探索効率を向上させる | 値が $3$ のノードに遭遇した場合、それ以上探索しない | +| 用語 | 定義 | 例題3 | +| ---------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| 解(solution) | 問題の特定の条件を満たす答えであり、1 つまたは複数存在し得る | 根ノードからノード $7$ までの、制約条件を満たすすべての経路 | +| 制約条件(constraint) | 解の実現可能性を制限する条件であり、通常は枝刈りに用いられる | 経路にノード $3$ を含まないこと | +| 状態(state) | ある時点における問題の状況を表し、すでに行った選択を含む | 現在までに訪問したノードの経路、すなわち `path` ノードリスト | +| 試行(attempt) | 利用可能な選択肢にもとづいて解空間を探索する過程であり、選択、状態更新、解判定を含む | 左右の子ノードを再帰的に訪問し、ノードを `path` に追加し、値が $7$ か判定する | +| 戻る(backtracking) | 制約条件を満たさない状態に出会ったとき、それまでの選択を取り消して前の状態へ戻ること | 葉ノードを越えたとき、ノード訪問を終えたとき、値が $3$ のノードに出会ったときに探索を終了し、関数から戻る | +| 枝刈り(pruning) | 問題の性質や制約条件にもとづき、無意味な探索経路を避ける方法であり、探索効率を高める | 値が $3$ のノードに出会ったら、それ以上探索しない | !!! tip - 問題、解、状態などの概念は一般的なものであり、分割統治、バックトラッキング、動的計画法、貪欲法などのアルゴリズムにも関係します。 + 問題、解、状態などの概念は汎用的であり、分割統治、バックトラッキング、動的計画法、貪欲法などのアルゴリズムにも共通して現れます。 -## 長所と限界 +## 利点と限界 -バックトラッキングアルゴリズムは本質的に深さ優先探索(DFS)アルゴリズムの一種であり、条件を満たす解を見つけるまであらゆる可能な解を試みます。この方法の利点は、すべての可能な解を見つけられる点であり、適切な枝刈りを行えば効率が高いことです。 +バックトラッキングアルゴリズムの本質は深さ優先探索です。条件を満たす解を見つけるまで、あり得るすべての解を試します。この方法の利点は、考えられるすべての解を見つけられることであり、適切な枝刈りを行えば高い効率を発揮します。 -しかし、大規模または複雑な問題を扱う場合、**バックトラッキングアルゴリズムの実行効率は許容できないほど低下する可能性があります**。 +しかし、大規模または複雑な問題を扱う場合、**バックトラッキングアルゴリズムの実行効率は受け入れがたいことがあります**。 -- **時間**:バックトラッキングアルゴリズムは通常、状態空間のすべての可能性を探索する必要があり、時間計算量は指数オーダーまたは階乗オーダーに達する可能性があります。 -- **空間**:再帰呼び出し中に現在の状態(例:経路、枝刈り用の補助変数など)を保存する必要があり、深さが大きい場合、空間の使用量が増加します。 +- **時間**:バックトラッキングアルゴリズムでは通常、状態空間のすべての可能性をたどる必要があり、時間計算量は指数時間や階乗時間に達することがあります。 +- **空間**:再帰呼び出しの過程では現在の状態(たとえば経路や枝刈り用の補助変数など)を保持する必要があり、深さが大きいと空間使用量も大きくなります。 -それでもなお、**バックトラッキングアルゴリズムは特定の探索問題や制約満足問題の最良の解法であることが多いです**。これらの問題では、どの選択が有効な解を生成するかを予測できないため、すべての可能な選択を試す必要があります。このような場合、**効率の最適化が鍵**となります。一般的な最適化手法は次の 2 つです。 +それでもなお、**バックトラッキングアルゴリズムは一部の探索問題や制約充足問題に対する最良の解法です**。この種の問題では、どの選択が有効な解を生むかを事前に予測できないため、可能な選択肢をすべてたどる必要があります。このときの鍵は**いかに効率を最適化するか**であり、代表的な方法は 2 つあります。 -- **枝刈り**:解を生成しないことが確実な経路を避けることで、時間と空間を節約します。 -- **ヒューリスティック探索**:探索中に戦略や評価値を導入し、有効な解を生成する可能性が高い経路を優先的に探索します。 +- **枝刈り**:解が生じないことが確実な経路を探索しないことで、時間と空間を節約する。 +- **ヒューリスティック探索**:探索中に何らかの戦略や推定値を導入し、有効な解を生みやすい経路を優先的に探索する。 -## バックトラッキングの典型的な例題 +## バックトラッキングの典型例題 -バックトラッキングアルゴリズムは、多くの探索問題、制約満足問題、組合せ最適化問題を解くのに使用できます。 +バックトラッキングアルゴリズムは、多くの探索問題、制約充足問題、組合せ最適化問題の解決に利用できます。 **探索問題**:この種の問題の目標は、特定の条件を満たす解を見つけることです。 -- 全順列問題:与えられた集合のすべての可能な順列を求める。 -- 部分和問題:与えられた集合と目標和に対して、和が目標値になるすべての部分集合を求める。 -- ハノイの塔:3 本の柱と異なるサイズの円盤があり、すべての円盤を 1 本の柱から別の柱に移す。1 回に 1 枚しか動かせず、大きな円盤を小さい円盤の上に置くことはできない。 +- 全順列問題:ある集合が与えられたとき、考えられるすべての順列を求める。 +- 部分和問題:ある集合と目標和が与えられたとき、和が目標値となるすべての部分集合を見つける。 +- ハノイの塔問題:3 本の柱と大きさの異なる複数の円盤が与えられたとき、すべての円盤を 1 本の柱から別の柱へ移動する。ただし 1 回に 1 枚しか動かせず、大きい円盤を小さい円盤の上に置いてはならない。 -**制約満足問題**:この種の問題の目標は、すべての制約条件を満たす解を見つけることです。 +**制約充足問題**:この種の問題の目標は、すべての制約条件を満たす解を見つけることです。 -- $n$ クイーン問題:$n imes n$ のチェス盤に $n$ 個のクイーンを配置し、互いに攻撃しないようにする。 -- 数独:$9 imes 9$ のグリッドに数字 $1$ \~ $9$ を入力し、各行、列、$3 imes 3$ のサブグリッドに重複がないようにする。 -- グラフ彩色問題:与えられた無向グラフに対し、隣接頂点が異なる色になるように最小限の色で彩色する。 +- $n$ クイーン問題:$n \times n$ の盤面に $n$ 個のクイーンを配置し、互いに攻撃し合わないようにする。 +- 数独:$9 \times 9$ のグリッドに数字 $1$ ~ $9$ を入れ、各行・各列・各 $3 \times 3$ の小区画で数字が重複しないようにする。 +- グラフ彩色問題:無向グラフが与えられたとき、隣接する頂点が同じ色にならないように、できるだけ少ない色で各頂点を彩色する。 -**組合せ最適化問題**:この種の問題の目標は、組合せ空間内で特定の条件を満たす最適解を見つけることです。 +**組合せ最適化問題**:この種の問題の目標は、組合せ空間の中で条件を満たす最適解を見つけることです。 -- 0-1 ナップサック問題:与えられた物品群とバックパックがあり、各物品には価値と重さが設定されている。バックパックの容量制限内で、総価値を最大化する物品の選択を求める。 -- 旅行セールスマン問題:グラフ上で、1 つの点から出発し、すべての他の点を 1 回ずつ訪問して出発点に戻る最短経路を求める。 -- 最大クリーク問題:与えられた無向グラフの中で、任意の 2 頂点間に辺が存在する最大の完全部分グラフを見つける。 +- 0-1 ナップサック問題:複数の品物とナップサックが与えられ、各品物には価値と重さがある。ナップサック容量の範囲内で総価値が最大になるように品物を選ぶ。 +- 巡回セールスマン問題:グラフ内のある頂点から出発し、他のすべての頂点をちょうど 1 回ずつ訪れて出発点へ戻るときの最短経路を求める。 +- 最大クリーク問題:無向グラフが与えられたとき、任意の 2 頂点間に辺が存在する最大の完全部分グラフを見つける。 -注意すべきは、多くの組合せ最適化問題に対して、バックトラッキングが最適解法ではないということです。 +多くの組合せ最適化問題では、バックトラッキングは最適な解法ではない点に注意してください。 -- 0-1 ナップサック問題は、時間効率を高めるために動的計画法がよく使用されます。 -- 旅行セールスマン問題は有名な NP-Hard 問題であり、遺伝的アルゴリズムやアントコロニーアルゴリズムなどの手法がよく使われます。 -- 最大クリーク問題はグラフ理論の古典的な問題であり、貪欲法などのヒューリスティックアルゴリズムで解くことができます。 +- 0-1 ナップサック問題は通常、より高い時間効率を得るために動的計画法で解く。 +- 巡回セールスマン問題は著名な NP-Hard 問題であり、よく用いられる解法には遺伝的アルゴリズムや蟻コロニー最適化などがある。 +- 最大クリーク問題はグラフ理論における古典的問題であり、貪欲法などのヒューリスティックで解ける。 diff --git a/ja/docs/chapter_backtracking/index.md b/ja/docs/chapter_backtracking/index.md index 0e2af6eeb..df5033922 100644 --- a/ja/docs/chapter_backtracking/index.md +++ b/ja/docs/chapter_backtracking/index.md @@ -4,6 +4,6 @@ !!! abstract - 迷路の探検家のように、私たちは前進する道で障害に遭遇することがあります。 - - バックトラッキングの力は、私たちに新しく始めること、試し続けること、そして最終的に光への出口を見つけることを可能にします。 + 私たちは迷宮の探検者のように、前へ進む道で困難に出会うことがあります。 + + バックトラッキングの力によってやり直しができ、試行を重ね、最後には光へ通じる出口を見つけられます。 diff --git a/ja/docs/chapter_backtracking/n_queens_problem.md b/ja/docs/chapter_backtracking/n_queens_problem.md index e75f4169f..79c44bd69 100644 --- a/ja/docs/chapter_backtracking/n_queens_problem.md +++ b/ja/docs/chapter_backtracking/n_queens_problem.md @@ -1,53 +1,53 @@ -# Nクイーン問題 +# n クイーン問題 !!! question - チェスのルールによると、クイーンは同じ行、列、または対角線上の駒を攻撃できます。$n$ 個のクイーンと $n \times n$ のチェスボードが与えられた場合、2つのクイーンが互いに攻撃できない配置を見つけてください。 + チェスのルールによれば、クイーンは同じ行、同じ列、または同じ斜線上にある駒を攻撃できます。$n$ 個のクイーンと $n \times n$ サイズの盤面が与えられたとき、すべてのクイーンが互いに攻撃し合わない配置を求めます。 -以下の図に示すように、$n = 4$ の場合、2つの解があります。バックトラッキングアルゴリズムの観点から、$n \times n$ のチェスボードには $n^2$ 個のマスがあり、すべての可能な選択肢 `choices` を示しています。チェスボードの状態 `state` は、各クイーンが配置されるにつれて継続的に変化します。 +下図に示すように、$n = 4$ のとき、2 つの解を見つけることができます。バックトラッキングの観点から見ると、$n \times n$ サイズの盤面には合計 $n^2$ 個のマスがあり、これがすべての選択肢 `choices` を与えます。クイーンを 1 つずつ配置していく過程で、盤面の状態は絶えず変化し、その各時点の盤面が状態 `state` です。 -![4クイーン問題の解](n_queens_problem.assets/solution_4_queens.png) +![4 クイーン問題の解](n_queens_problem.assets/solution_4_queens.png) -以下の図は、この問題の3つの制約を示しています:**複数のクイーンは同じ行、列、または対角線を占有できません**。対角線は主対角線 `\` と副対角線 `/` に分かれることに注意することが重要です。 +下図は本問題の 3 つの制約条件を示しています。**複数のクイーンは同じ行、同じ列、同じ対角線上に置けません**。なお、対角線には主対角線 `\` と副対角線 `/` の 2 種類があります。 -![Nクイーン問題の制約](n_queens_problem.assets/n_queens_constraints.png) +![n クイーン問題の制約条件](n_queens_problem.assets/n_queens_constraints.png) ### 行ごとの配置戦略 -クイーンの数がチェスボードの行数と等しく、どちらも $n$ であるため、**チェスボードの各行には1つのクイーンのみが配置できることが**容易に結論付けられます。 +クイーンの数と盤面の行数はいずれも $n$ なので、次の推論を容易に得られます:**盤面の各行にはクイーンを 1 つだけ配置できます**。 -これは、行ごとの配置戦略を採用できることを意味します:最初の行から開始して、最後の行に到達するまで行ごとに1つのクイーンを配置します。 +つまり、行ごとの配置戦略を採用できます:最初の行から始めて、各行に 1 つのクイーンを配置し、最後の行まで進みます。 -以下の図は、4クイーン問題の行ごとの配置プロセスを示しています。スペースの制限により、図は最初の行の1つの検索分岐のみを展開し、列と対角線の制約を満たさない配置を剪定します。 +下図は 4 クイーン問題における行ごとの配置過程を示しています。図の大きさの都合上、下図では 1 行目における検索分岐の 1 つだけを展開し、列制約と対角線制約を満たさない案はすべて枝刈りしています。 ![行ごとの配置戦略](n_queens_problem.assets/n_queens_placing.png) -本質的に、**行ごとの配置戦略は剪定関数として機能し**、同じ行に複数のクイーンを配置するすべての検索分岐を除去します。 +本質的には、**行ごとの配置戦略は枝刈りとして機能します**。これにより、同じ行に複数のクイーンが現れるすべての探索分岐を回避できます。 -### 列と対角線の剪定 +### 列と対角線の枝刈り -列の制約を満たすために、長さ $n$ のブール配列 `cols` を使用して、各列にクイーンが占有されているかどうかを追跡できます。各配置決定の前に、`cols` を使用してすでにクイーンがある列を剪定し、バックトラッキング中に動的に更新されます。 +列制約を満たすために、長さ $n$ のブール配列 `cols` を用いて、各列にクイーンがあるかどうかを記録できます。配置を決めるたびに、`cols` を使って既存のクイーンがある列を枝刈りし、バックトラッキングの中で `cols` の状態を動的に更新します。 !!! tip - 行列の原点は左上隅にあり、行インデックスは上から下に増加し、列インデックスは左から右に増加することに注意してください。 + 注意として、行列の原点は左上にあり、行インデックスは上から下へ、列インデックスは左から右へ増加します。 -対角線の制約はどうでしょうか?チェスボード上の特定のセルの行と列のインデックスを $(row, col)$ とします。特定の主対角線を選択することで、その対角線上のすべてのセルで差 $row - col$ が同じであることに気付きます。**つまり、$row - col$ は主対角線上で定数値です**。 +では、対角線制約はどのように扱えばよいのでしょうか。盤面上のあるマスの行列インデックスを $(row, col)$ とし、行列内のある主対角線を選ぶと、その対角線上のすべてのマスで行インデックスから列インデックスを引いた値が等しいことが分かります。**つまり、主対角線上のすべてのマスでは $row - col$ が一定値になります**。 -言い換えると、2つのセルが $row_1 - col_1 = row_2 - col_2$ を満たす場合、それらは確実に同じ主対角線上にあります。このパターンを使用して、以下の図に示す配列 `diags1` を利用して、クイーンが主対角線上にあるかどうかを追跡できます。 +つまり、2 つのマスが $row_1 - col_1 = row_2 - col_2$ を満たすなら、それらは必ず同じ主対角線上にあります。この性質を利用して、下図の配列 `diags1` により、各主対角線にクイーンがあるかどうかを記録できます。 -同様に、**$row + col$ の和は副対角線上のすべてのセルで定数値です**。配列 `diags2` を使用して副対角線の制約も処理できます。 +同様に、**副対角線上のすべてのマスでは $row + col$ が一定値です**。副対角線制約も配列 `diags2` を使って処理できます。 -![列と対角線の制約の処理](n_queens_problem.assets/n_queens_cols_diagonals.png) +![列制約と対角線制約の処理](n_queens_problem.assets/n_queens_cols_diagonals.png) ### コード実装 -$n$ 次元の正方行列では、$row - col$ の範囲は $[-n + 1, n - 1]$ で、$row + col$ の範囲は $[0, 2n - 2]$ であることに注意してください。したがって、主対角線と副対角線の数はどちらも $2n - 1$ で、配列 `diags1` と `diags2` の長さは $2n - 1$ です。 +注意として、$n$ 次正方行列では $row - col$ の範囲は $[-n + 1, n - 1]$ 、$row + col$ の範囲は $[0, 2n - 2]$ です。したがって、主対角線と副対角線の本数はいずれも $2n - 1$ であり、配列 `diags1` と `diags2` の長さもともに $2n - 1$ です。 ```src [file]{n_queens}-[class]{}-[func]{n_queens} ``` -$n$ 個のクイーンを行ごとに配置し、列の制約を考慮して、最初の行から最後の行まで、$n$、$n-1$、$\dots$、$2$、$1$ の選択肢があり、$O(n!)$ 時間を使用します。解を記録する際、行列 `state` をコピーして `res` に追加する必要があり、コピー操作は $O(n^2)$ 時間を使用します。したがって、**全体の時間計算量は $O(n! \cdot n^2)$ です**。実際には、対角線制約に基づく剪定により検索空間を大幅に削減できるため、多くの場合、検索効率は上記の時間計算量よりも優れています。 +行ごとに $n$ 回配置し、列制約を考慮すると、1 行目から最終行までの選択肢はそれぞれ $n$、$n-1$、$\dots$、$2$、$1$ 個となるため、時間計算量は $O(n!)$ です。解を記録する際には、行列 `state` をコピーして `res` に追加する必要があり、このコピー操作には $O(n^2)$ 時間を要します。したがって、**全体の時間計算量は $O(n! \cdot n^2)$** です。実際には、対角線制約による枝刈りも探索空間を大きく縮小できるため、探索効率はしばしば上記の時間計算量より良くなります。 -配列 `state` は $O(n^2)$ 空間を使用し、配列 `cols`、`diags1`、`diags2` はそれぞれ $O(n)$ 空間を使用します。最大再帰深度は $n$ で、$O(n)$ のスタックフレーム空間を使用します。したがって、**空間計算量は $O(n^2)$ です**。 +配列 `state` は $O(n^2)$ の空間を使用し、配列 `cols`、`diags1`、`diags2` はいずれも $O(n)$ の空間を使用します。最大再帰深さは $n$ で、スタックフレーム空間として $O(n)$ を使用します。したがって、**空間計算量は $O(n^2)$** です。 diff --git a/ja/docs/chapter_backtracking/permutations_problem.md b/ja/docs/chapter_backtracking/permutations_problem.md index b47d97f51..e0b96f228 100644 --- a/ja/docs/chapter_backtracking/permutations_problem.md +++ b/ja/docs/chapter_backtracking/permutations_problem.md @@ -1,95 +1,95 @@ -# 順列問題 +# 全順列問題 -順列問題は、バックトラッキングアルゴリズムの典型的な応用です。これは、配列や文字列などの与えられた集合から要素のすべての可能な配置(順列)を見つけることを含みます。 +全順列問題はバックトラッキングアルゴリズムの典型的な応用例です。これは、ある集合(配列や文字列など)が与えられたとき、その要素のあり得るすべての順列を求める問題です。 -以下の表は、入力配列とその対応する順列を含むいくつかの例を示しています。 +下表に、入力配列とそれに対応するすべての順列から成る例をいくつか示します。 -

  順列の例

+

  全順列の例

-| 入力配列 | 順列 | -| :----------- | :----------------------------------------------------------------- | -| $[1]$ | $[1]$ | -| $[1, 2]$ | $[1, 2], [2, 1]$ | -| $[1, 2, 3]$ | $[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]$ | +| 入力配列 | すべての順列 | +| :---------- | :----------------------------------------------------------------- | +| $[1]$ | $[1]$ | +| $[1, 2]$ | $[1, 2], [2, 1]$ | +| $[1, 2, 3]$ | $[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]$ | -## 重複要素がない場合 +## 等しい要素がない場合 !!! question - 重複要素のない整数配列が与えられた場合、すべての可能な順列を返してください。 + 重複要素を含まない整数配列を入力として受け取り、あり得るすべての順列を返します。 -バックトラッキングの観点から、**順列を生成するプロセスを一連の選択として見ることができます。** 入力配列が $[1, 2, 3]$ だとします。最初に $1$ を選択し、次に $3$、最後に $2$ を選択すると、順列 $[1, 3, 2]$ が得られます。「バックトラッキング」は前の選択を取り消して、代替オプションを探索することを意味します。 +バックトラッキングアルゴリズムの観点から見ると、**順列生成の過程は一連の選択の結果として捉えられます**。入力配列が $[1, 2, 3]$ だとすると、最初に $1$ を選び、次に $3$ を選び、最後に $2$ を選べば、順列 $[1, 3, 2]$ が得られます。戻る操作は 1 つの選択を取り消し、その後で別の選択を試し続けることを表します。 -コーディングの観点から、候補集合 `choices` は入力配列のすべての要素で構成され、`state` はこれまでに選択された要素を保持します。各要素は一度だけ選択できるため、**`state` のすべての要素は一意である必要があります**。 +バックトラッキングコードの観点では、候補集合 `choices` は入力配列中のすべての要素であり、状態 `state` は現時点までに選ばれた要素です。各要素は 1 回しか選べないことに注意してください。**したがって `state` 内の要素はすべて一意でなければなりません**。 -以下の図に示すように、検索プロセスを再帰木に展開できます。各ノードは現在の `state` を表します。ルートノードから開始して、3回の選択の後、葉ノードに到達します—それぞれが順列に対応します。 +下図のように、探索過程は再帰木として展開できます。木の各ノードは現在の状態 `state` を表します。根ノードから始めて 3 ラウンドの選択を経て葉ノードに到達し、各葉ノードが 1 つの順列に対応します。 -![順列の再帰木](permutations_problem.assets/permutations_i.png) +![全順列の再帰木](permutations_problem.assets/permutations_i.png) -### 重複選択の剪定 +### 重複選択の枝刈り -各要素が一度だけ選択されることを保証するために、ブール配列 `selected` を導入します。ここで `selected[i]` は `choices[i]` が選択されたかどうかを示します。次に、この配列に基づいて剪定ステップを実行します: +各要素が 1 回しか選ばれないようにするため、ブール配列 `selected` の導入を考えます。ここで `selected[i]` は `choices[i]` がすでに選ばれているかどうかを表し、これに基づいて次の枝刈りを行います。 -- `choice[i]` を選択した後、`selected[i]` を $\text{True}$ に設定して選択されたとマークします。 -- `choices` を反復処理する際、選択されたとマークされたすべての要素をスキップします(つまり、それらの分岐を剪定します)。 +- 選択 `choice[i]` を行った後、`selected[i]` を $\text{True}$ に設定し、その要素が選択済みであることを表します。 +- 選択肢リスト `choices` を走査するとき、すでに選ばれたノードはすべてスキップします。これが枝刈りです。 -以下の図に示すように、最初のラウンドで1を選択し、2番目のラウンドで3を選択し、最後のラウンドで2を選択するとします。2番目のラウンドで要素1の分岐と、3番目のラウンドで要素1と3の分岐を剪定する必要があります。 +下図のように、1 回目に 1、2 回目に 3、3 回目に 2 を選ぶとします。このとき 2 回目では要素 1 の分岐を、3 回目では要素 1 と要素 3 の分岐を刈り取る必要があります。 -![順列の剪定例](permutations_problem.assets/permutations_i_pruning.png) +![全順列の枝刈り例](permutations_problem.assets/permutations_i_pruning.png) -図から、この剪定プロセスが検索空間を $O(n^n)$ から $O(n!)$ に削減することがわかります。 +上図から、この枝刈りにより探索空間の大きさは $O(n^n)$ から $O(n!)$ へ削減されることがわかります。 ### コード実装 -この理解により、フレームワークコードの「空欄を埋める」ことができます。全体のコードを簡潔に保つため、フレームワークの各部分を個別に実装せず、代わりに `backtrack()` 関数ですべてを展開します: +以上を整理できれば、フレームワークコードの「穴埋め」を行えます。全体のコードを短くするため、フレームワークコード中の各関数を個別には実装せず、これらを `backtrack()` 関数内に展開します。 ```src [file]{permutations_i}-[class]{}-[func]{permutations_i} ``` -## 重複要素を考慮する場合 +## 等しい要素を考慮する場合 !!! question - **重複要素を含む可能性のある**整数配列が与えられた場合、すべての一意の順列を返してください。 + 整数配列を入力として受け取り、**配列には重複要素が含まれる場合があります**。重複しない順列をすべて返します。 -入力配列が $[1, 1, 2]$ だとします。2つの同一要素 $1$ を区別するために、2番目を $\hat{1}$ とラベル付けします。 +入力配列が $[1, 1, 2]$ だと仮定します。2 つの重複する要素 $1$ を区別しやすくするため、2 つ目の $1$ を $\hat{1}$ と記します。 -以下の図に示すように、この方法で生成される順列の半分は重複です: +下図のように、上述の方法で生成される順列の半分は重複しています。 -![重複順列](permutations_problem.assets/permutations_ii.png) +![重複した順列](permutations_problem.assets/permutations_ii.png) -では、これらの重複順列をどのように除去できるでしょうか?一つの直接的なアプローチは、すべての順列を生成した後にハッシュセットを使用して重複を除去することです。しかし、これはあまり優雅ではありません。**重複を生成する分岐は本来不要であり、事前に剪定されるべきだからです**、これによりアルゴリズムの効率が向上します。 +では、重複した順列をどのように取り除けばよいのでしょうか。最も直接的なのは、ハッシュ集合を用いて順列結果をそのまま重複排除する方法です。しかしこのやり方は十分に洗練されていません。**なぜなら、重複順列を生成する探索分岐はそもそも不要であり、事前に見つけて枝刈りすべきだからです**。そうすることで、アルゴリズム効率をさらに高められます。 -### 等値要素の剪定 +### 等しい要素の枝刈り -以下の図を見ると、最初のラウンドで $1$ または $\hat{1}$ を選択すると同じ順列につながるため、$\hat{1}$ を剪定します。 +下図を見ると、1 回目のラウンドでは $1$ を選ぶことと $\hat{1}$ を選ぶことは等価であり、これら 2 つの選択の下で生成される順列はすべて重複します。したがって $\hat{1}$ を枝刈りすべきです。 -同様に、最初のラウンドで $2$ を選択した後、2番目のラウンドで $1$ または $\hat{1}$ を選択しても重複分岐につながるため、その時も $\hat{1}$ を剪定します。 +同様に、1 回目で $2$ を選んだ後では、2 回目のラウンドにおける $1$ と $\hat{1}$ も重複分岐を生むため、2 回目の $\hat{1}$ も枝刈りすべきです。 -本質的に、**私たちの目標は、複数の同一要素が選択の各ラウンドで一度だけ選択されることを保証することです。** +本質的には、**各ラウンドの選択において、等しい複数の要素が 1 回しか選ばれないようにすることが目標です**。 -![重複順列の剪定](permutations_problem.assets/permutations_ii_pruning.png) +![重複順列の枝刈り](permutations_problem.assets/permutations_ii_pruning.png) ### コード実装 -前の問題のコードに基づいて、各ラウンドでハッシュセット `duplicated` を導入します。このセットは、すでに試行した要素を追跡し、重複を剪定できるようにします: +前問のコードを土台として、各ラウンドの選択でハッシュ集合 `duplicated` を 1 つ用意し、そのラウンドですでに試した要素を記録して、重複要素を枝刈りすることを考えます。 ```src [file]{permutations_ii}-[class]{}-[func]{permutations_ii} ``` -すべての要素が異なると仮定すると、$n$ 個の要素の順列は $n!$ (階乗)個あります。各結果を記録するには長さ $n$ のリストをコピーする必要があり、これには $O(n)$ 時間がかかります。**したがって、総時間計算量は $O(n!n)$ です。** +要素どうしがすべて互いに異なると仮定すると、$n$ 個の要素には全部で $n!$ 通りの順列(階乗)があります。結果を記録する際には、長さ $n$ のリストをコピーする必要があり、これに $O(n)$ 時間を要します。**したがって時間計算量は $O(n!n)$** です。 -最大再帰深度は $n$ で、$O(n)$ のスタック空間を使用します。`selected` 配列も $O(n)$ 空間が必要です。一度に最大 $n$ 個の個別の `duplicated` セットが存在する可能性があるため、それらは集合的に $O(n^2)$ 空間を占有します。**したがって、空間計算量は $O(n^2)$ です。** +再帰の最大深さは $n$ であり、$O(n)$ のスタックフレーム空間を使います。`selected` は $O(n)$ 空間を使用します。同時刻に存在する `duplicated` は最大で $n$ 個であり、$O(n^2)$ 空間を要します。**したがって空間計算量は $O(n^2)$** です。 -### 2つの剪定方法の比較 +### 2 種類の枝刈りの比較 -`selected` と `duplicated` はどちらも剪定メカニズムとして機能しますが、異なる問題をターゲットにしています: +`selected` と `duplicated` はどちらも枝刈りに用いられますが、目的は異なる点に注意してください。 -- **重複選択の剪定**(`selected` 経由):検索全体に単一の `selected` 配列があり、現在の状態にすでにある要素を示します。これにより、同じ要素が `state` に複数回現れることを防ぎます。 -- **等値要素の剪定**(`duplicated` 経由):`backtrack` 関数の各呼び出しは独自の `duplicated` セットを使用し、その特定の反復(`for` ループ)ですでに選択された要素を記録します。これにより、等しい要素が選択の各ラウンドで一度だけ選択されることを保証します。 +- **重複選択の枝刈り**:探索全体を通して `selected` は 1 つだけです。これは現在の状態にどの要素が含まれているかを記録し、ある要素が `state` に重複して現れるのを防ぎます。 +- **等しい要素の枝刈り**:各ラウンドの選択、すなわち各回の `backtrack` 呼び出しには `duplicated` が含まれます。これはそのラウンドの走査(`for` ループ)でどの要素がすでに選ばれたかを記録し、等しい要素が 1 回しか選ばれないことを保証します。 -以下の図は、これら2つの剪定戦略の範囲を示しています。木の各ノードは選択を表します。ルートから任意の葉への経路は、1つの完全な順列に対応します。 +下図は、2 つの枝刈り条件が有効になる範囲を示しています。木の各ノードは 1 つの選択を表し、根ノードから葉ノードまでの経路上の各ノードが 1 つの順列を構成することに注意してください。 -![2つの剪定条件の範囲](permutations_problem.assets/permutations_ii_pruning_summary.png) +![2 種類の枝刈り条件の作用範囲](permutations_problem.assets/permutations_ii_pruning_summary.png) diff --git a/ja/docs/chapter_backtracking/subset_sum_problem.md b/ja/docs/chapter_backtracking/subset_sum_problem.md index 5a66425ad..52c3405f9 100644 --- a/ja/docs/chapter_backtracking/subset_sum_problem.md +++ b/ja/docs/chapter_backtracking/subset_sum_problem.md @@ -1,88 +1,88 @@ -# 部分集合和問題 +# 部分和問題 -## 重複要素がない場合 +## 重複しない要素の場合 !!! question - 正の整数の配列 `nums` とターゲット正整数 `target` が与えられた場合、組み合わせ内の要素の和が `target` に等しくなるようなすべての可能な組み合わせを見つけてください。与えられた配列には重複要素がなく、各要素は複数回選択できます。これらの組み合わせを重複する組み合わせを含まないリストとして返してください。 + 正整数配列 `nums` と目標の正整数 `target` が与えられたとき、要素の和が `target` に等しくなるすべての組合せを見つけてください。配列に重複要素はなく、各要素は複数回選択できます。これらの組合せをリスト形式で返してください。リストに重複する組合せを含めてはなりません。 -例えば、入力集合 $\{3, 4, 5\}$ とターゲット整数 $9$ の場合、解は $\{3, 3, 3\}, \{4, 5\}$ です。以下の2点に注意してください。 +例えば、入力集合 $\{3, 4, 5\}$ と目標整数 $9$ に対する解は $\{3, 3, 3\}, \{4, 5\}$ です。次の 2 点に注意してください。 -- 入力集合の要素は無制限に選択できます。 -- 部分集合は要素の順序を区別しません。例えば $\{4, 5\}$ と $\{5, 4\}$ は同じ部分集合です。 +- 入力集合内の要素は何度でも繰り返し選択できます。 +- 部分集合では要素の順序を区別しません。例えば $\{4, 5\}$ と $\{5, 4\}$ は同じ部分集合です。 -### 順列解法の参考 +### 全順列の解法を参考にする -順列問題と同様に、部分集合の生成を一連の選択として想像でき、選択プロセス中に「要素和」をリアルタイムで更新できます。要素和が `target` に等しくなったとき、部分集合を結果リストに記録します。 +全順列問題と同様に、部分集合の生成過程を一連の選択結果として捉え、選択の過程で「要素の和」を逐次更新できます。そして要素の和が `target` に等しくなった時点で、その部分集合を結果リストに記録します。 -順列問題とは異なり、**この問題では要素は無制限に選択できるため**、要素が選択されたかどうかを記録するための `selected` ブール配列を使用する必要がありません。順列コードに軽微な修正を加えて、最初に問題を解決できます: +ただし全順列問題と異なるのは、**この問題では集合内の要素を無制限に選択できる**点です。そのため、要素がすでに選択されたかどうかを記録する `selected` ブール配列は不要です。全順列のコードに少し修正を加えると、まず次の解法コードが得られます。 ```src [file]{subset_sum_i_naive}-[class]{}-[func]{subset_sum_i_naive} ``` -配列 $[3, 4, 5]$ とターゲット要素 $9$ を上記のコードに入力すると、結果 $[3, 3, 3], [4, 5], [5, 4]$ が得られます。**和が $9$ のすべての部分集合を正常に見つけましたが、重複する部分集合 $[4, 5]$ と $[5, 4]$ が含まれています**。 +上のコードに配列 $[3, 4, 5]$ と目標値 $9$ を入力すると、出力は $[3, 3, 3], [4, 5], [5, 4]$ となります。**和が $9$ となる部分集合はすべて見つかっていますが、重複する部分集合 $[4, 5]$ と $[5, 4]$ が含まれています**。 -これは、検索プロセスが選択の順序を区別するためですが、部分集合は選択順序を区別しません。以下の図に示すように、$5$ の前に $4$ を選択することと $4$ の前に $5$ を選択することは異なる分岐ですが、同じ部分集合に対応します。 +これは、探索過程では選択順を区別する一方で、部分集合では選択順を区別しないためです。次の図のように、先に $4$ を選んでから $5$ を選ぶ場合と、先に $5$ を選んでから $4$ を選ぶ場合は別の分岐ですが、対応する部分集合は同じです。 -![部分集合の検索と境界外の剪定](subset_sum_problem.assets/subset_sum_i_naive.png) +![部分集合探索と境界超過の枝刈り](subset_sum_problem.assets/subset_sum_i_naive.png) -重複する部分集合を除去するために、**直接的なアイデアは結果リストを重複除去することです**。しかし、この方法は2つの理由で非常に非効率的です。 +重複する部分集合を取り除くために、**直接的な方法として結果リストの重複を除去する**ことが考えられます。しかし、この方法は効率が低く、その理由は次の 2 点です。 -- 配列要素が多い場合、特に `target` が大きい場合、検索プロセスで大量の重複する部分集合が生成されます。 -- 部分集合(配列)の差異を比較することは非常に時間がかかり、まず配列をソートし、次に配列の各要素の差異を比較する必要があります。 +- 配列要素が多い場合、特に `target` が大きい場合には、探索過程で大量の重複部分集合が生成されます。 +- 部分集合(配列)同士の違いを比較するのは非常に時間がかかり、まず配列をソートし、その後に各要素を比較する必要があります。 -### 重複部分集合の剪定 +### 重複部分集合の枝刈り -**剪定を通じて検索プロセス中に重複除去を検討します**。以下の図を観察すると、異なる順序で配列要素を選択するときに重複する部分集合が生成されます。例えば、以下の状況です。 +**探索過程で枝刈りを行って重複を除去する**ことを考えます。次の図を観察すると、重複部分集合は配列要素を異なる順序で選択したときに生じます。例えば次のような状況です。 -1. 最初のラウンドで $3$ を選択し、2番目のラウンドで $4$ を選択すると、これら2つの要素を含むすべての部分集合が生成され、$[3, 4, \dots]$ と表記されます。 -2. 後で、最初のラウンドで $4$ が選択されたとき、**2番目のラウンドは $3$ をスキップすべきです**。この選択によって生成される部分集合 $[4, 3, \dots]$ はステップ `1.` の部分集合と完全に重複するからです。 +1. 1 回目と 2 回目でそれぞれ $3$ と $4$ を選ぶと、これら 2 要素を含むすべての部分集合、すなわち $[3, 4, \dots]$ が生成されます。 +2. その後、1 回目で $4$ を選んだ場合、**2 回目では $3$ をスキップすべき**です。というのも、この選択で生成される部分集合 $[4, 3, \dots]$ は、手順 `1.` で生成された部分集合と完全に重複するからです。 -検索プロセスでは、各層の選択が左から右に一つずつ試行されるため、右側の分岐ほどより多く剪定されます。 +探索過程では、各階層の選択は左から右へ順に試されるため、右側にある分岐ほど多く枝刈りされます。 -1. 最初の2ラウンドで $3$ と $5$ を選択し、部分集合 $[3, 5, \dots]$ を生成します。 -2. 最初の2ラウンドで $4$ と $5$ を選択し、部分集合 $[4, 5, \dots]$ を生成します。 -3. 最初のラウンドで $5$ が選択された場合、**2番目のラウンドは $3$ と $4$ をスキップすべきです**。部分集合 $[5, 3, \dots]$ と $[5, 4, \dots]$ はステップ `1.` と `2.` で記述された部分集合と完全に重複するからです。 +1. 最初の 2 回で $3$ と $5$ を選ぶと、部分集合 $[3, 5, \dots]$ が生成されます。 +2. 最初の 2 回で $4$ と $5$ を選ぶと、部分集合 $[4, 5, \dots]$ が生成されます。 +3. もし 1 回目で $5$ を選ぶなら、**2 回目では $3$ と $4$ をスキップすべき**です。なぜなら、部分集合 $[5, 3, \dots]$ と $[5, 4, \dots]$ は、手順 `1.` と手順 `2.` で述べた部分集合と完全に重複するからです。 -![異なる選択順序による重複部分集合](subset_sum_problem.assets/subset_sum_i_pruning.png) +![異なる選択順によって生じる重複部分集合](subset_sum_problem.assets/subset_sum_i_pruning.png) -要約すると、入力配列 $[x_1, x_2, \dots, x_n]$ が与えられた場合、検索プロセスでの選択シーケンスは $[x_{i_1}, x_{i_2}, \dots, x_{i_m}]$ であるべきで、$i_1 \leq i_2 \leq \dots \leq i_m$ を満たす必要があります。**この条件を満たさない選択シーケンスは重複を引き起こし、剪定されるべきです**。 +まとめると、入力配列 $[x_1, x_2, \dots, x_n]$ が与えられ、探索過程における選択列を $[x_{i_1}, x_{i_2}, \dots, x_{i_m}]$ とすると、この選択列は $i_1 \leq i_2 \leq \dots \leq i_m$ を満たす必要があります。**この条件を満たさない選択列は重複を生むため、枝刈りすべきです**。 ### コード実装 -この剪定を実装するために、変数 `start` を初期化し、これは走査の開始点を示します。**選択 $x_{i}$ を行った後、次のラウンドをインデックス $i$ から開始するように設定します**。これにより、選択シーケンスが $i_1 \leq i_2 \leq \dots \leq i_m$ を満たすことが保証され、部分集合の一意性が保証されます。 +この枝刈りを実現するために、走査の開始位置を示す変数 `start` を初期化します。**選択 $x_{i}$ を行った後、次のラウンドはインデックス $i$ から走査を開始する**ように設定します。これにより、選択列が $i_1 \leq i_2 \leq \dots \leq i_m$ を満たし、部分集合の一意性が保証されます。 -さらに、コードに以下の2つの最適化を行いました。 +これに加えて、コードには次の 2 つの最適化も施しています。 -- 検索を開始する前に、配列 `nums` をソートします。すべての選択の走査で、**部分集合和が `target` を超えたときにループを直接終了します**。後続の要素はより大きく、それらの部分集合和は確実に `target` を超えるからです。 -- 要素和変数 `total` を除去し、**`target` に対して減算を実行して要素和をカウントします**。`target` が $0$ に等しくなったとき、解を記録します。 +- 探索を始める前に、まず配列 `nums` をソートします。すべての選択肢を走査するとき、**部分集合の和が `target` を超えたら直ちにループを終了**します。後続の要素はさらに大きいため、その和も必ず `target` を超えるからです。 +- 要素和を保持する変数 `total` は省略し、**`target` から減算することで要素和を管理**します。`target` が $0$ になったときに解を記録します。 ```src [file]{subset_sum_i}-[class]{}-[func]{subset_sum_i} ``` -以下の図は、配列 $[3, 4, 5]$ とターゲット要素 $9$ を上記のコードに入力した後の全体的なバックトラッキングプロセスを示しています。 +次の図は、配列 $[3, 4, 5]$ と目標値 $9$ を上のコードに入力したときの、全体のバックトラッキング過程を示しています。 -![部分集合和 I のバックトラッキングプロセス](subset_sum_problem.assets/subset_sum_i.png) +![部分和 I のバックトラッキング過程](subset_sum_problem.assets/subset_sum_i.png) -## 重複要素がある場合を考慮 +## 重複要素を考慮する場合 !!! question - 正の整数の配列 `nums` とターゲット正整数 `target` が与えられた場合、組み合わせ内の要素の和が `target` に等しくなるようなすべての可能な組み合わせを見つけてください。**与えられた配列には重複要素が含まれる可能性があり、各要素は一度だけ選択できます**。これらの組み合わせを重複する組み合わせを含まないリストとして返してください。 + 正整数配列 `nums` と目標の正整数 `target` が与えられたとき、要素の和が `target` に等しくなるすべての組合せを見つけてください。**与えられた配列には重複要素が含まれる可能性があり、各要素は 1 回しか選択できません**。これらの組合せをリスト形式で返してください。リストに重複する組合せを含めてはなりません。 -前の問題と比較して、**この問題の入力配列には重複要素が含まれる可能性があり**、新しい問題が導入されます。例えば、配列 $[4, \hat{4}, 5]$ とターゲット要素 $9$ が与えられた場合、既存のコードの出力結果は $[4, 5], [\hat{4}, 5]$ となり、重複する部分集合が生成されます。 +前問と比べると、**この問題の入力配列には重複要素が含まれる可能性があります**。そのため、新たな問題が生じます。例えば、配列 $[4, \hat{4}, 5]$ と目標値 $9$ が与えられると、既存コードの出力は $[4, 5], [\hat{4}, 5]$ となり、重複部分集合が現れます。 -**この重複の理由は、特定のラウンドで等しい要素が複数回選択されることです**。以下の図では、最初のラウンドに3つの選択肢があり、そのうち2つが $4$ であり、2つの重複する検索分岐を生成し、重複する部分集合を出力します。同様に、2番目のラウンドの2つの $4$ も重複する部分集合を生成します。 +**この重複が生じる原因は、同じ値の要素があるラウンドで複数回選ばれてしまうことにあります**。次の図では、1 回目には 3 つの選択肢があり、そのうち 2 つはどちらも $4$ です。これにより 2 本の重複した探索分岐が生じ、重複部分集合が出力されます。同様に、2 回目の 2 つの $4$ も重複部分集合を生みます。 -![等しい要素による重複部分集合](subset_sum_problem.assets/subset_sum_ii_repeat.png) +![等しい要素によって生じる重複部分集合](subset_sum_problem.assets/subset_sum_ii_repeat.png) -### 等値要素の剪定 +### 等しい要素の枝刈り -この問題を解決するために、**等しい要素がラウンドごとに一度だけ選択されるように制限する必要があります**。実装は非常に巧妙です:配列がソートされているため、等しい要素は隣接しています。これは、特定のラウンドの選択で、現在の要素がその左側の要素と等しい場合、それはすでに選択されていることを意味するため、現在の要素を直接スキップします。 +この問題を解決するには、**各ラウンドで等しい要素が 1 回しか選ばれないように制限する必要があります**。実装方法は巧妙です。配列はすでにソートされているため、等しい要素は必ず隣り合っています。したがって、あるラウンドの選択で現在の要素が左隣の要素と等しいなら、それはすでに選ばれたことを意味するので、その要素を直接スキップします。 -同時に、**この問題では各配列要素は一度だけ選択できると規定されています**。幸い、変数 `start` を使用してこの制約も満たすことができます:選択 $x_{i}$ を行った後、次のラウンドをインデックス $i + 1$ から前方に開始するように設定します。これにより、重複する部分集合が除去されるだけでなく、要素の重複選択も回避されます。 +同時に、**この問題では各配列要素を 1 回しか選択できない**という制約もあります。幸い、この制約も変数 `start` を使って満たせます。すなわち、選択 $x_{i}$ を行った後、次のラウンドはインデックス $i + 1$ から後ろへ走査するよう設定します。これにより、重複部分集合を除去できるだけでなく、同じ要素を繰り返し選ぶことも防げます。 ### コード実装 @@ -90,6 +90,6 @@ [file]{subset_sum_ii}-[class]{}-[func]{subset_sum_ii} ``` -以下の図は、配列 $[4, 4, 5]$ とターゲット要素 $9$ のバックトラッキングプロセスを示し、4種類の剪定操作が含まれています。図とコードのコメントを組み合わせて、検索プロセス全体と各種類の剪定操作の動作を理解してください。 +次の図は、配列 $[4, 4, 5]$ と目標値 $9$ に対するバックトラッキング過程を示しており、全部で 4 種類の枝刈り操作が含まれています。図とコードコメントを対応させながら、探索全体の流れと、各枝刈り操作がどのように機能するかを理解してください。 -![部分集合和 II のバックトラッキングプロセス](subset_sum_problem.assets/subset_sum_ii.png) +![部分和 II のバックトラッキング過程](subset_sum_problem.assets/subset_sum_ii.png) diff --git a/ja/docs/chapter_backtracking/summary.md b/ja/docs/chapter_backtracking/summary.md index 796df949c..df041e78b 100644 --- a/ja/docs/chapter_backtracking/summary.md +++ b/ja/docs/chapter_backtracking/summary.md @@ -1,23 +1,23 @@ # まとめ -### 重要な復習 +### 重要なポイントの振り返り -- バックトラッキングアルゴリズムの本質は全数探索です。解空間の深さ優先走査を実行することで条件を満たす解を求めます。検索中に満足のいく解が見つかった場合、それを記録し、すべての解が見つかるか走査が完了するまで続けます。 -- バックトラッキングアルゴリズムの検索プロセスには試行と後退が含まれます。深さ優先探索を使用して様々な選択を探索し、選択が制約を満たさない場合、前の選択を取り消します。そして前の状態に戻って他のオプションを試し続けます。試行と後退は反対方向の操作です。 -- バックトラッキング問題には通常複数の制約が含まれます。これらの制約は剪定操作を実行するために使用できます。剪定は不要な検索分岐を事前に終了し、検索効率を大幅に向上させることができます。 -- バックトラッキングアルゴリズムは主に検索問題と制約満足問題を解決するために使用されます。組み合わせ最適化問題はバックトラッキングを使用して解決できますが、多くの場合、より効率的または効果的な解決方法が利用可能です。 -- 順列問題は、与えられた集合の要素のすべての可能な順列を検索することを目的とします。各要素が選択されたかどうかを記録するために配列を使用し、同じ要素の重複選択を避けます。これにより、各要素が一度だけ選択されることが保証されます。 -- 順列問題では、集合に重複要素が含まれている場合、最終結果に重複順列が含まれます。同一要素が各ラウンドで一度だけ選択できるように制限する必要があり、これは通常ハッシュセットを使用して実装されます。 -- 部分集合和問題は、与えられた集合でターゲット値に合計する全ての部分集合を見つけることを目的とします。集合は要素の順序を区別しませんが、検索プロセスでは重複する部分集合が生成される可能性があります。これは、アルゴリズムが異なる要素順序を独特のパスとして探索するために発生します。バックトラッキングの前に、データをソートし、各ラウンドの走査の開始点を示す変数を設定します。これにより、重複する部分集合を生成する検索分岐を剪定できます。 -- 部分集合和問題では、配列内の等しい要素は重複集合を生成する可能性があります。配列がすでにソートされているという前提条件を使用して、隣接する要素が等しいかどうかを判定することで剪定を行います。これにより、等しい要素がラウンドごとに一度だけ選択されることが保証されます。 -- $n$ クイーン問題は、2つのクイーンが互いに攻撃できないように $n \times n$ のチェスボードに $n$ 個のクイーンを配置する方案を見つけることを目的とします。問題の制約には行制約、列制約、および主対角線と副対角線の制約が含まれます。行制約を満たすために、行ごとに1つのクイーンを配置する戦略を採用し、各行に1つのクイーンが配置されることを保証します。 -- 列制約と対角線制約の処理は似ています。列制約については、各列にクイーンがあるかどうかを記録する配列を使用し、選択されたセルが合法かどうかを示します。対角線制約については、2つの配列を使用して主対角線と副対角線にそれぞれクイーンの存在を記録します。課題は、同じ主対角線または副対角線上のセルの行と列のインデックス間の関係を決定することです。 +- バックトラッキングアルゴリズムの本質は全探索法であり、解空間を深さ優先で走査することで条件を満たす解を探索します。探索の過程で条件を満たす解に出会ったら記録し、すべての解を見つけるか探索が完了するまで続けます。 +- バックトラッキングアルゴリズムの探索過程は、試行と戻るという 2 つの部分から成ります。深さ優先探索によってさまざまな選択を試し、制約条件を満たさない状況に遭遇した場合は直前の選択を取り消して前の状態に戻り、ほかの選択を引き続き試します。試行と戻るは互いに逆方向の操作です。 +- バックトラッキング問題には通常複数の制約条件が含まれており、それらを枝刈りに利用できます。枝刈りによって不要な探索分岐を早期に打ち切り、探索効率を大幅に高められます。 +- バックトラッキングアルゴリズムは主に探索問題と制約充足問題の解決に用いられます。組合せ最適化問題もバックトラッキングで解けますが、より高効率またはより適した解法が存在することが少なくありません。 +- 全順列問題の目的は、与えられた集合要素のすべての可能な並べ方を探索することです。各要素が選択済みかどうかを配列で記録し、同じ要素を重複して選ぶ探索分岐を刈り取ることで、各要素が 1 度だけ選ばれるようにします。 +- 全順列問題では、集合内に重複要素があると最終結果にも重複した順列が現れます。各ラウンドで等しい要素は 1 回しか選べないように制約する必要があり、通常はハッシュ集合を用いて実現します。 +- 部分和問題の目標は、与えられた集合の中から和が目標値となるすべての部分集合を見つけることです。集合では要素順序を区別しませんが、探索過程では順序違いの結果も出力されるため、重複部分集合が生じます。そこで、バックトラッキング前にデータをソートし、各ラウンドの走査開始位置を示す変数を設定することで、重複部分集合を生成する探索分岐を枝刈りします。 +- 部分和問題では、配列中の等しい要素が重複集合を生みます。配列がソート済みであるという前提を利用し、隣接要素が等しいかどうかを判定して枝刈りすることで、等しい要素が各ラウンドで 1 回しか選ばれないようにします。 +- $n$ クイーン問題の目的は、$n \times n$ の盤面に $n$ 個のクイーンを配置する方法を見つけることであり、どの 2 つのクイーンも互いに攻撃できないことが条件です。この問題の制約には行制約、列制約、主対角線制約、副対角線制約があります。行制約を満たすため、行ごとに配置する戦略を採用し、各行に 1 個のクイーンを置くことを保証します。 +- 列制約と対角線制約の扱い方は似ています。列制約については、各列にクイーンが存在するかどうかを配列で記録し、選択したマスが有効かどうかを判定します。対角線制約については、主対角線と副対角線それぞれにクイーンが存在するかを 2 つの配列で記録します。難点は、同じ主対角線または副対角線上にあるマスが満たす行列インデックスの規則を見つけることにあります。 ### Q & A -**Q**: バックトラッキングと再帰の関係をどのように理解すればよいですか? +**Q**:バックトラッキングと再帰の関係はどのように理解すればよいですか? -全体的に、バックトラッキングは「アルゴリズム戦略」であり、再帰はより「ツール」です。 +全体として見ると、バックトラッキングは「アルゴリズム戦略」の一種であり、再帰はむしろ「道具」に近いものです。 -- バックトラッキングアルゴリズムは通常再帰に基づいています。しかし、バックトラッキングは再帰の応用シナリオの一つであり、特に検索問題においてです。 -- 再帰の構造は「部分問題分解」の問題解決パラダイムを反映します。分割統治、バックトラッキング、動的プログラミング(メモ化再帰)を含む問題の解決でよく使用されます。 +- バックトラッキングアルゴリズムは通常、再帰に基づいて実装されます。ただし、バックトラッキングは再帰の応用場面の 1 つであり、探索問題における再帰の応用です。 +- 再帰の構造は「部分問題への分解」という問題解決パラダイムを表しており、分割統治、バックトラッキング、動的計画法(メモ化再帰)などの問題によく用いられます。 diff --git a/ja/docs/chapter_computational_complexity/index.md b/ja/docs/chapter_computational_complexity/index.md index ce8f0ae0d..1b040a1e8 100644 --- a/ja/docs/chapter_computational_complexity/index.md +++ b/ja/docs/chapter_computational_complexity/index.md @@ -1,9 +1,9 @@ -# 複雑度解析 +# 計算量解析 -![Complexity analysis](../assets/covers/chapter_complexity_analysis.jpg) +![計算量解析](../assets/covers/chapter_complexity_analysis.jpg) !!! abstract - 複雑度解析は、アルゴリズムの広大な宇宙における時空のナビゲーターのようなものです。 - - 時間と空間の次元をより深く探求し、より優雅な解決策を求めるためのガイドとなります。 + 計算量解析は、広大なアルゴリズム宇宙における時空の案内人のようなものです。 + + それは、時間と空間という二つの次元で私たちをより深く探求へ導き、より洗練された解決策を見つけ出します。 diff --git a/ja/docs/chapter_computational_complexity/iteration_and_recursion.md b/ja/docs/chapter_computational_complexity/iteration_and_recursion.md index b18c00785..62caef822 100644 --- a/ja/docs/chapter_computational_complexity/iteration_and_recursion.md +++ b/ja/docs/chapter_computational_complexity/iteration_and_recursion.md @@ -1,194 +1,194 @@ # 反復と再帰 -アルゴリズムにおいて、タスクの繰り返し実行は非常に一般的であり、複雑度の分析と密接に関係しています。したがって、時間計算量と空間計算量の概念を詳しく学ぶ前に、まずプログラミングで繰り返しタスクを実装する方法を探究しましょう。これには、2つの基本的なプログラミング制御構造である反復と再帰の理解が含まれます。 +アルゴリズムでは、ある処理を繰り返し実行することがよくあり、これは複雑度解析と密接に関係しています。そのため、時間計算量と空間計算量を紹介する前に、まずプログラム内で反復実行を実現する方法、つまり 2 つの基本的な制御構造である反復と再帰について見ていきます。 ## 反復 -反復は、タスクを繰り返し実行するための制御構造です。反復では、プログラムは特定の条件が満たされている限りコードブロックを繰り返し実行し、この条件が満たされなくなるまで続けます。 +反復(iteration)は、ある処理を繰り返し実行するための制御構造です。反復では、プログラムは一定の条件を満たす間、あるコード片を繰り返し実行し、その条件を満たさなくなるまで続けます。 -### forループ +### for ループ -`for`ループは反復の最も一般的な形式の1つであり、**反復回数が事前に分かっている場合に特に適しています**。 +`for` ループは最も一般的な反復形式の 1 つで、**反復回数があらかじめ分かっている場合に適しています**。 -以下の関数は`for`ループを使用して$1 + 2 + \dots + n$の合計を実行し、合計を変数`res`に格納します。Pythonでは、`range(a, b)`は`a`を含み`b`を除く区間を作成することに注意してください。つまり、$a$から$b−1$までの範囲で反復します。 +次の関数は `for` ループを用いて $1 + 2 + \dots + n$ の総和を計算しており、その結果は変数 `res` に記録されます。なお、Python の `range(a, b)` に対応する区間は「左閉右開」であり、走査範囲は $a, a + 1, \dots, b-1$ です。 ```src [file]{iteration}-[class]{}-[func]{for_loop} ``` -以下の図はこの合計関数を表しています。 +次の図は、この総和関数のフローチャートです。 -![Flowchart of the sum function](iteration_and_recursion.assets/iteration.png) +![総和関数のフローチャート](iteration_and_recursion.assets/iteration.png) -この合計関数での操作数は入力データのサイズ$n$に比例する、つまり線形関係があります。**この「線形関係」こそが時間計算量が記述するものです**。このトピックについては次のセクションで詳しく説明します。 +この総和関数の操作回数は入力データサイズ $n$ に比例し、言い換えれば「線形関係」にあります。実際、**時間計算量が記述するのはこの「線形関係」そのものです**。関連内容は次節で詳しく説明します。 -### whileループ +### while ループ -`for`ループと同様に、`while`ループは反復を実装するためのもう1つのアプローチです。`while`ループでは、プログラムは各反復の開始時に条件をチェックし、条件が真の場合は実行を継続し、そうでなければループを終了します。 +`for` ループと同様に、`while` ループも反復を実現する方法の 1 つです。`while` ループでは、各反復のたびにまず条件を確認し、条件が真であれば実行を続け、そうでなければループを終了します。 -以下では`while`ループを使用して合計$1 + 2 + \dots + n$を実装します。 +次に、`while` ループを使って $1 + 2 + \dots + n$ の総和を求めてみましょう。 ```src [file]{iteration}-[class]{}-[func]{while_loop} ``` -**`while`ループは`for`ループよりも柔軟性を提供します**。特に、条件変数のカスタム初期化と各ステップでの変更が可能です。 +**`while` ループは `for` ループより自由度が高い**です。`while` ループでは、条件変数の初期化や更新手順を柔軟に設計できます。 -例えば、以下のコードでは、条件変数$i$が各ラウンドで2回更新されますが、これは`for`ループでは実装が不便です。 +たとえば次のコードでは、条件変数 $i$ が各反復で 2 回更新されており、このようなケースは `for` ループではあまり扱いやすくありません。 ```src [file]{iteration}-[class]{}-[func]{while_loop_ii} ``` -全体的に、**`for`ループはより簡潔で、`while`ループはより柔軟です**。どちらも反復構造を実装できます。どちらを使用するかは、問題の具体的な要件に基づいて決定する必要があります。 +総じて、**`for` ループのコードはより簡潔で、`while` ループはより柔軟**です。どちらも反復構造を実現できますが、どちらを使うかは問題ごとの要件に応じて決めるべきです。 ### ネストしたループ -1つのループ構造を別のループ構造内にネストできます。以下は`for`ループを使用した例です: +1 つのループ構造の中に別のループ構造を入れ子にできます。以下では `for` ループを例にします。 ```src [file]{iteration}-[class]{}-[func]{nested_for_loop} ``` -以下の図はこのネストしたループを表しています。 +次の図は、このネストしたループのフローチャートです。 -![Flowchart of the nested loop](iteration_and_recursion.assets/nested_iteration.png) +![ネストしたループのフローチャート](iteration_and_recursion.assets/nested_iteration.png) -このような場合、関数の操作数は$n^2$に比例します。つまり、アルゴリズムの実行時間と入力データのサイズ$n$には「二次関係」があります。 +この場合、関数の操作回数は $n^2$ に比例し、言い換えればアルゴリズムの実行時間は入力データサイズ $n$ と「二次関係」にあります。 -さらにネストしたループを追加することで複雑度を高めることができ、各レベルのネストは事実上「次元を増加」させ、時間計算量を「三次」、「四次」などに引き上げます。 +さらにネストしたループを追加することもできます。ネストが 1 段増えるたびに「次元が 1 つ上がる」ことになり、時間計算量は「三次関係」「四次関係」へと高くなっていきます。 ## 再帰 -再帰は、関数が自分自身を呼び出すことで問題を解決するアルゴリズム戦略です。主に2つのフェーズが含まれます: + 再帰(recursion)は、関数が自分自身を呼び出すことで問題を解決するアルゴリズム戦略です。主に 2 つの段階から成ります。 -1. **呼び出し**: プログラムが自分自身を繰り返し呼び出し、しばしばより小さいまたはより単純な引数で、「終了条件」に向かって進みます。 -2. **返却**: 「終了条件」がトリガーされると、プログラムは最も深い再帰関数から返り始め、各レイヤーの結果を集約します。 +1. **再帰呼び出し**:プログラムは自分自身をより深く呼び出し続け、通常はより小さい、またはより単純化された引数を渡し、「終了条件」に達するまで進みます。 +2. **復帰**: 「終了条件」が満たされると、プログラムは最も深い再帰関数から 1 層ずつ戻り、各層の結果をまとめていきます。 -実装の観点から、再帰コードは主に3つの要素を含みます。 +実装の観点から見ると、再帰コードは主に 3 つの要素から成ります。 -1. **終了条件**: 「呼び出し」から「返却」にいつ切り替えるかを決定します。 -2. **再帰呼び出し**: 「呼び出し」に対応し、関数が自分自身を呼び出し、通常はより小さいまたはより単純化されたパラメータで行います。 -3. **結果の返却**: 「返却」に対応し、現在の再帰レベルの結果が前のレイヤーに返されます。 +1. **終了条件**:いつ再帰呼び出しから復帰へ切り替わるかを決めます。 +2. **再帰呼び出し**:再帰呼び出しに対応し、関数が自分自身を呼び出します。通常はより小さい、またはより単純化された引数を入力します。 +3. **結果の返却**:復帰に対応し、現在の再帰レベルの結果を 1 つ上の層へ返します。 -以下のコードを観察してください。単純に関数`recur(n)`を呼び出すだけで$1 + 2 + \dots + n$の合計を計算できます: +次のコードを見ると、関数 `recur(n)` を呼び出すだけで $1 + 2 + \dots + n$ を計算できます。 ```src [file]{recursion}-[class]{}-[func]{recur} ``` -以下の図はこの関数の再帰プロセスを示しています。 +次の図は、この関数の再帰過程を示しています。 -![Recursive process of the sum function](iteration_and_recursion.assets/recursion_sum.png) +![総和関数の再帰過程](iteration_and_recursion.assets/recursion_sum.png) -反復と再帰は計算の観点から同じ結果を達成できますが、**それらは思考と問題解決の全く異なるパラダイムを表します**。 +計算の観点では、反復と再帰は同じ結果を得られますが、**それらは問題を考え解決するためのまったく異なる 2 つのパラダイムを表しています**。 -- **反復**: 「ボトムアップ」で問題を解決します。最も基本的なステップから始まり、タスクが完了するまでこれらのステップを繰り返し追加または累積します。 -- **再帰**: 「トップダウン」で問題を解決します。元の問題をより小さなサブ問題に分解し、各サブ問題は元の問題と同じ形式を持ちます。これらのサブ問題は、解が分かっているベースケースで停止するまで、さらに小さなサブ問題に分解されます。 +- **反復**:「ボトムアップ」で問題を解決します。最も基本的な手順から始め、それらを繰り返したり積み上げたりして、処理が完了するまで進めます。 +- **再帰**:「トップダウン」で問題を解決します。元の問題をより小さな部分問題に分解し、それらの部分問題は元の問題と同じ形を持ちます。さらに部分問題をより小さな部分問題へと分解し、基本ケースに達したところで停止します(基本ケースの解は既知です)。 -先ほどの合計関数の例を取ってみましょう。$f(n) = 1 + 2 + \dots + n$として定義されます。 +前述の総和関数を例に、問題を $f(n) = 1 + 2 + \dots + n$ とします。 -- **反復**: このアプローチでは、ループ内で合計プロセスをシミュレートします。$1$から始まり$n$まで横断し、各反復で合計操作を実行して最終的に$f(n)$を計算します。 -- **再帰**: ここでは、問題はサブ問題に分解されます:$f(n) = n + f(n-1)$。この分解は、ベースケースの$f(1) = 1$に到達するまで再帰的に続き、そこで再帰が終了します。 +- **反復**:ループ内で総和の過程を模擬し、$1$ から $n$ まで走査して、各反復で加算を行えば $f(n)$ を求められます。 +- **再帰**:問題を部分問題 $f(n) = n + f(n-1)$ に分解し、これを再帰的に分解し続け、基本ケース $f(1) = 1$ に達したところで終了します。 ### 呼び出しスタック -再帰関数が自分自身を呼び出すたびに、システムは新しく開始された関数にメモリを割り当てて、ローカル変数、戻りアドレス、その他の関連情報を格納します。これは2つの主要な結果をもたらします。 +再帰関数が自分自身を呼び出すたびに、システムは新たに開始された関数のためにメモリを割り当て、局所変数、呼び出し先アドレス、その他の情報を保存します。これにより 2 つの結果が生じます。 -- 関数のコンテキストデータは「スタックフレーム空間」と呼ばれるメモリ領域に格納され、関数が返された後にのみ解放されます。したがって、**再帰は一般的に反復よりも多くのメモリ空間を消費します**。 -- 再帰呼び出しは追加のオーバーヘッドを導入します。**したがって、再帰は通常ループよりも時間効率が劣ります。** +- 関数のコンテキストデータは「スタックフレーム領域」と呼ばれるメモリ領域に保存され、関数が戻るまで解放されません。したがって、**再帰は通常、反復より多くのメモリ空間を消費します**。 +- 再帰による関数呼び出しには追加のオーバーヘッドが発生します。**そのため再帰は通常、ループより時間効率が低くなります**。 -以下の図に示されているように、終了条件がトリガーされる前に$n$個の未返却の再帰関数があり、**再帰の深さが$n$であることを示しています**。 +次の図のように、終了条件が発動する前には、まだ戻っていない再帰関数が同時に $n$ 個存在し、**再帰の深さは $n$** になります。 -![Recursion call depth](iteration_and_recursion.assets/recursion_sum_depth.png) +![再帰呼び出しの深さ](iteration_and_recursion.assets/recursion_sum_depth.png) -実際には、プログラミング言語で許可される再帰の深さは通常制限されており、過度に深い再帰はスタックオーバーフローエラーを引き起こす可能性があります。 +実際には、プログラミング言語が許容する再帰の深さには通常上限があり、深すぎる再帰はスタックオーバーフローを引き起こす可能性があります。 ### 末尾再帰 -興味深いことに、**関数が返す直前の最後のステップとして再帰呼び出しを実行する場合**、コンパイラまたはインタープリターによって反復と同じ空間効率になるように最適化できます。このシナリオは末尾再帰として知られています。 +興味深いことに、**関数が返る直前の最後の処理で再帰呼び出しを行う場合**、その関数はコンパイラやインタプリタによって最適化され、空間効率が反復と同程度になることがあります。これを末尾再帰(tail recursion)と呼びます。 -- **通常の再帰**: 標準的な再帰では、関数が前のレベルに戻ったとき、さらにコードを実行し続けるため、システムは前の呼び出しのコンテキストを保存する必要があります。 -- **末尾再帰**: ここでは、再帰呼び出しは関数が返す前の最終操作です。これは、前のレベルに戻った際に、さらなるアクションが必要ないことを意味するため、システムは前のレベルのコンテキストを保存する必要がありません。 +- **通常の再帰**:関数が 1 つ上の階層の関数へ戻った後も、引き続きコードを実行する必要があるため、システムは 1 つ上の呼び出しのコンテキストを保存しておく必要があります。 +- **末尾再帰**:再帰呼び出しが関数の返却前の最後の操作であるため、1 つ上の階層へ戻った後に他の処理を続ける必要がなく、システムは 1 つ上の関数のコンテキストを保存する必要がありません。 -例えば、$1 + 2 + \dots + n$の計算では、結果変数`res`を関数のパラメータにすることで、末尾再帰を実現できます: +$1 + 2 + \dots + n$ の計算を例にすると、結果変数 `res` を関数の引数にすることで、末尾再帰を実現できます。 ```src [file]{recursion}-[class]{}-[func]{tail_recur} ``` -末尾再帰の実行プロセスは以下の図に示されています。通常の再帰と末尾再帰を比較すると、合計操作のポイントが異なります。 +末尾再帰の実行過程を次の図に示します。通常の再帰と末尾再帰を比べると、加算処理が実行されるタイミングが異なります。 -- **通常の再帰**: 合計操作は「返却」フェーズで発生し、各レイヤーが返った後にもう一度合計が必要です。 -- **末尾再帰**: 合計操作は「呼び出し」フェーズで発生し、「返却」フェーズは各レイヤーを通じて返すだけです。 +- **通常の再帰**:加算処理は復帰の過程で実行され、各層が戻るたびにもう一度加算を行います。 +- **末尾再帰**:加算処理は再帰呼び出しの過程で実行され、復帰の過程では各層が戻るだけで済みます。 -![Tail recursion process](iteration_and_recursion.assets/tail_recursion_sum.png) +![末尾再帰の過程](iteration_and_recursion.assets/tail_recursion_sum.png) !!! tip - 多くのコンパイラやインタープリターは末尾再帰最適化をサポートしていないことに注意してください。例えば、Pythonはデフォルトで末尾再帰最適化をサポートしていないため、関数が末尾再帰の形式であっても、スタックオーバーフローの問題に遭遇する可能性があります。 + 多くのコンパイラやインタプリタは末尾再帰最適化をサポートしていない点に注意してください。たとえば、Python はデフォルトで末尾再帰最適化をサポートしていないため、関数が末尾再帰の形であっても、スタックオーバーフローが発生する可能性があります。 ### 再帰木 -「分割統治」に関連するアルゴリズムを扱う際、再帰は反復よりもしばしばより直感的なアプローチとより読みやすいコードを提供します。「フィボナッチ数列」を例に取ってみましょう。 +「分割統治」に関連するアルゴリズム問題を扱う際、再帰は反復よりも発想が直感的で、コードも読みやすいことがよくあります。「フィボナッチ数列」を例に見てみましょう。 !!! question - フィボナッチ数列$0, 1, 1, 2, 3, 5, 8, 13, \dots$が与えられた場合、数列の$n$番目の数を求めなさい。 + フィボナッチ数列 $0, 1, 1, 2, 3, 5, 8, 13, \dots$ が与えられたとき、この数列の第 $n$ 項を求めてください。 -フィボナッチ数列の$n$番目の数を$f(n)$とすると、2つの結論を簡単に導き出せます: +フィボナッチ数列の第 $n$ 項を $f(n)$ とすると、次の 2 つが容易に分かります。 -- 数列の最初の2つの数は$f(1) = 0$と$f(2) = 1$です。 -- 数列の各数は前の2つの数の合計です。つまり、$f(n) = f(n - 1) + f(n - 2)$です。 +- 数列の最初の 2 項は $f(1) = 0$ と $f(2) = 1$ です。 +- 数列中の各項は直前の 2 項の和であり、すなわち $f(n) = f(n - 1) + f(n - 2)$ です。 -再帰関係を使用し、最初の2つの数を終了条件として考慮すると、再帰コードを書けます。`fib(n)`を呼び出すとフィボナッチ数列の$n$番目の数が得られます: +漸化式に従って再帰呼び出しを行い、最初の 2 項を終了条件とすれば、再帰コードを書けます。`fib(n)` を呼び出すことでフィボナッチ数列の第 $n$ 項を得られます。 ```src [file]{recursion}-[class]{}-[func]{fib} ``` -上記のコードを観察すると、それ自体の中で2つの関数を再帰的に呼び出していることがわかります。**つまり、1回の呼び出しで2つの分岐呼び出しが生成されます**。以下の図に示されているように、この継続的な再帰呼び出しは最終的に深さ$n$の再帰木を作成します。 +上のコードを見ると、関数内で 2 回の再帰呼び出しを行っています。**これは 1 回の呼び出しから 2 つの呼び出し分岐が生じることを意味します**。次の図のように、この再帰呼び出しを繰り返していくと、最終的に深さ $n$ の再帰木(recursion tree)が生成されます。 -![Fibonacci sequence recursion tree](iteration_and_recursion.assets/recursion_tree.png) +![フィボナッチ数列の再帰木](iteration_and_recursion.assets/recursion_tree.png) -基本的に、再帰は「問題をより小さなサブ問題に分解する」パラダイムを体現しています。この分割統治戦略は重要です。 +本質的に見ると、再帰は「問題をより小さな部分問題へ分解する」という思考パラダイムを体現しており、この分割統治の戦略は非常に重要です。 -- アルゴリズムの観点から、探索、ソート、バックトラッキング、分割統治、動的プログラミングなどの多くの重要な戦略は、直接的または間接的にこの思考方法を使用しています。 -- データ構造の観点から、再帰は連結リスト、木、グラフを扱うのに自然に適しており、これらは分割統治アプローチを使用した分析に適しているためです。 +- アルゴリズムの観点では、探索、ソート、バックトラッキング、分割統治、動的計画法など、多くの重要な戦略が直接または間接にこの考え方を用いています。 +- データ構造の観点では、再帰は連結リスト、木、グラフに関する問題の処理に本質的に適しており、これらは分割統治の考え方で分析しやすいからです。 -## 比較 +## 両者の比較 -上記の内容をまとめると、以下の表は実装、性能、適用性の観点から反復と再帰の違いを示しています。 +以上をまとめると、次の表のように、反復と再帰は実装、性能、適用性の面で違いがあります。 -

表: 反復と再帰の特性の比較

+

  反復と再帰の特徴の比較

-| | 反復 | 再帰 | -| ----------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------- | -| アプローチ | ループ構造 | 関数が自分自身を呼び出す | -| 時間効率 | 一般的により高い効率、関数呼び出しのオーバーヘッドなし | 各関数呼び出しがオーバーヘッドを生成 | -| メモリ使用量 | 通常は固定サイズのメモリ空間を使用 | 累積的な関数呼び出しが大量のスタックフレーム空間を使用する可能性 | -| 適用可能な問題 | 単純なループタスクに適している、直感的で読みやすいコード | 問題の分解に適している(木、グラフ、分割統治、バックトラッキングなど)、簡潔で明確なコード構造 | +| | 反復 | 再帰 | +| -------- | -------------------------------------- | ------------------------------------------------------------ | +| 実装方法 | ループ構造 | 関数が自分自身を呼び出す | +| 時間効率 | 通常は効率が高く、関数呼び出しの負荷がない | 関数呼び出しのたびにオーバーヘッドが発生する | +| メモリ使用 | 通常は固定サイズのメモリ空間を使う | 関数呼び出しの蓄積により大量のスタックフレーム領域を使う可能性がある | +| 適用対象 | 単純な反復処理に適し、コードが直感的で読みやすい | 木、グラフ、分割統治、バックトラッキングなどの部分問題分解に適し、コード構造が簡潔で明快 | !!! tip - 以下の内容が理解しにくい場合は、「スタック」の章を読んだ後に再び訪れることを検討してください。 + 以下の内容が難しいと感じる場合は、「スタック」の章を読み終えた後に改めて復習してください。 -それでは、反復と再帰の本質的な関連は何でしょうか?上記の再帰関数を例に取ると、合計操作は再帰の「返却」フェーズで発生します。これは、最初に呼び出された関数が最後に合計操作を完了することを意味し、**スタックの「後入れ先出し」原理を反映しています**。 +では、反復と再帰にはどのような内在的な関係があるのでしょうか。前述の再帰関数を例にすると、加算処理は再帰の復帰段階で行われます。これは、最初に呼び出された関数が実際には最後に加算を完了することを意味しており、**この動作の仕組みはスタックの「後入れ先出し」の原則とよく似ています**。 -「呼び出しスタック」や「スタックフレーム空間」などの再帰用語は、再帰とスタックの密接な関係を示しています。 +実際、「呼び出しスタック」や「スタックフレーム領域」といった再帰の用語自体が、再帰とスタックの密接な関係を示唆しています。 -1. **呼び出し**: 関数が呼び出されると、システムは「呼び出しスタック」上にその関数用の新しいスタックフレームを割り当て、ローカル変数、パラメータ、戻りアドレス、その他のデータを格納します。 -2. **返却**: 関数が実行を完了して返ると、対応するスタックフレームが「呼び出しスタック」から削除され、前の関数の実行環境が復元されます。 +1. **再帰呼び出し**:関数が呼び出されると、システムは「呼び出しスタック」上にその関数のための新しいスタックフレームを割り当て、局所変数、引数、返却先アドレスなどのデータを保存します。 +2. **復帰**:関数の実行が完了して戻ると、対応するスタックフレームは「呼び出しスタック」から取り除かれ、前の関数の実行環境が復元されます。 -したがって、**明示的なスタックを使用して呼び出しスタックの動作をシミュレートできます**。これにより再帰を反復形式に変換できます: +したがって、**明示的なスタックを使って呼び出しスタックの振る舞いを模擬することができ**、その結果として再帰を反復形式へ変換できます。 ```src [file]{recursion}-[class]{}-[func]{for_loop_recur} ``` -上記のコードを観察すると、再帰が反復に変換されたとき、コードはより複雑になります。反復と再帰はしばしば相互に変換できますが、2つの理由でそうすることが常に推奨されるわけではありません: +上のコードを見ると、再帰を反復へ変換すると、コードはより複雑になります。反復と再帰は多くの場合に相互変換できますが、常にそうする価値があるとは限りません。理由は次の 2 点です。 -- 変換されたコードは理解がより困難になり、読みにくくなる可能性があります。 -- 一部の複雑な問題では、システムの呼び出しスタックの動作をシミュレートすることは非常に困難です。 +- 変換後のコードは理解しにくくなり、可読性が下がる可能性があります。 +- 複雑な問題によっては、システムの呼び出しスタックの振る舞いを模擬すること自体が非常に難しい場合があります。 -結論として、**反復または再帰を選択するかは問題の具体的な性質によります**。プログラミングの実践では、両方の長所と短所を比較検討し、手元の状況に最も適したアプローチを選択することが重要です。 +要するに、**反復を選ぶか再帰を選ぶかは、対象となる問題の性質によって決まります**。実際のプログラミングでは、両者の長所と短所を見極め、状況に応じて適切な方法を選ぶことが重要です。 diff --git a/ja/docs/chapter_computational_complexity/performance_evaluation.md b/ja/docs/chapter_computational_complexity/performance_evaluation.md index 065be4ebf..522fb8264 100644 --- a/ja/docs/chapter_computational_complexity/performance_evaluation.md +++ b/ja/docs/chapter_computational_complexity/performance_evaluation.md @@ -1,49 +1,49 @@ -# アルゴリズムの効率評価 +# アルゴリズム効率の評価 -アルゴリズム設計において、私たちは順序に従って以下の2つの目標を追求します。 +アルゴリズム設計では、次の 2 つのレベルの目標を順に追求します。 -1. **問題の解決策を見つける**: アルゴリズムは、指定された入力範囲内で確実に正しい解を見つけることができるべきです。 -2. **最適解を求める**: 同じ問題に対して複数の解決策が存在する場合があり、私たちは可能な限り最も効率的なアルゴリズムを見つけることを目指します。 +1. **問題の解法を見つける**:アルゴリズムは、定められた入力範囲内で問題の正しい解を確実に求められる必要があります。 +2. **最適な解法を追求する**:同じ問題に対して複数の解法が存在する場合があり、私たちはできるだけ効率的なアルゴリズムを見つけたいと考えます。 -つまり、問題を解決できることを前提として、アルゴリズムの効率がアルゴリズムを評価する主要な基準となっており、これには以下の2つの次元が含まれます。 +つまり、問題を解けることを前提として、アルゴリズム効率はその良し悪しを測る主要な評価指標となっており、次の 2 つの観点を含みます。 -- **時間効率**: アルゴリズムが実行される速度。 -- **空間効率**: アルゴリズムが占有するメモリ空間のサイズ。 +- **時間効率**:アルゴリズムの実行時間の長さ。 +- **空間効率**:アルゴリズムが使用するメモリ空間の大きさ。 -要するに、**私たちの目標は、高速でメモリ効率の良いデータ構造とアルゴリズムを設計することです**。アルゴリズムの効率を効果的に評価することは重要です。なぜなら、そうすることで初めて様々なアルゴリズムを比較し、アルゴリズムの設計と最適化プロセスを導くことができるからです。 +簡単に言えば、**私たちの目標は「高速で省メモリ」なデータ構造とアルゴリズムを設計すること**です。そして、アルゴリズム効率を効果的に評価することは非常に重要です。そうすることで初めて、さまざまなアルゴリズムを比較し、さらにアルゴリズム設計と最適化の過程を導けるからです。 -効率評価には主に2つの方法があります:実際のテストと理論的推定です。 +効率の評価方法は主に 2 種類に分けられます。実測と理論的な見積もりです。 -## 実際のテスト +## 実測 -アルゴリズム`A`と`B`があり、どちらも同じ問題を解決でき、それらの効率を比較する必要があるとします。最も直接的な方法は、コンピュータを使用してこれら2つのアルゴリズムを実行し、実行時間とメモリ使用量を監視・記録することです。この評価方法は実際の状況を反映しますが、大きな制限があります。 +いまアルゴリズム `A` とアルゴリズム `B` があり、どちらも同じ問題を解けるとします。この 2 つのアルゴリズムの効率を比較する必要がある場合、最も直接的な方法は 1 台のコンピュータで両者を実行し、その実行時間とメモリ使用量を監視して記録することです。この評価方法は実際の状況を反映できますが、大きな制約もあります。 -一方で、**テスト環境からの干渉を排除することは困難です**。ハードウェア構成はアルゴリズムの性能に影響を与える可能性があります。例えば、並列度の高いアルゴリズムはマルチコアCPUでの実行により適していますし、集約的なメモリ操作を含むアルゴリズムは高性能メモリでより良い性能を発揮します。アルゴリズムのテスト結果は、異なるマシン間で変わる可能性があります。これは、平均効率を計算するために複数のマシンでテストすることが実用的でないことを意味します。 +一方では、**テスト環境による干渉要因を排除しにくい**という問題があります。ハードウェア構成はアルゴリズムの性能に影響します。たとえば、並列度の高いアルゴリズムはマルチコア CPU での実行により適しており、メモリアクセスが集中的なアルゴリズムは高性能メモリ上でより良い性能を示します。つまり、異なるマシンでのテスト結果は一致しない可能性があります。これは、さまざまなマシンでテストして平均効率を統計的に求める必要があることを意味しますが、それは現実的ではありません。 -一方で、**完全なテストを実施することは非常にリソース集約的です**。アルゴリズムの効率は入力データサイズによって変わります。例えば、データ量が少ない場合はアルゴリズム`A`が`B`より速く実行される可能性がありますが、データ量が多い場合はテスト結果が逆になる可能性があります。したがって、説得力のある結論を導くためには、幅広い入力データサイズをテストする必要があり、これには過度な計算リソースが必要になります。 +他方では、**完全なテストを実施するには非常に多くの資源が必要**です。入力データ量が変化すると、アルゴリズムは異なる効率を示します。たとえば、入力データ量が小さいときはアルゴリズム `A` の実行時間がアルゴリズム `B` より短くても、入力データ量が大きいときには結果がちょうど逆になるかもしれません。そのため、説得力のある結論を得るには、さまざまな規模の入力データでテストする必要があり、それには大量の計算資源を要します。 -## 理論的推定 +## 理論的な見積もり -実際のテストの大きな制限により、計算のみでアルゴリズムの効率を評価することを検討できます。この推定方法は漸近的複雑度解析、または単に複雑度解析として知られています。 +実測には大きな制約があるため、いくつかの計算だけによってアルゴリズムの効率を評価することを考えられます。この見積もり方法は漸近計算量解析(asymptotic complexity analysis)と呼ばれ、略して計算量解析といいます。 -複雑度解析は、アルゴリズムの実行に必要な時間と空間リソースと入力データのサイズとの関係を反映します。**これは、入力データのサイズが増加するにつれて、アルゴリズムに必要な時間と空間の増加傾向を記述します**。この定義は複雑に聞こえるかもしれませんが、より良く理解するために3つの重要なポイントに分解できます。 +計算量解析は、アルゴリズムの実行に必要な時間資源と空間資源が入力データ規模とどのような関係にあるかを表します。**これは、入力データ規模が増加するにつれて、アルゴリズムの実行に必要な時間と空間がどのように増加するかという傾向を記述するものです**。この定義はややわかりにくいので、次の 3 つのポイントに分けて理解できます。 -- 「時間と空間リソース」は、それぞれ時間計算量空間計算量に対応します。 -- 「入力データのサイズが増加するにつれて」は、複雑度がアルゴリズムの効率と入力データ量との関係を反映することを意味します。 -- 「時間と空間の増加傾向」は、複雑度解析が実行時間や占有空間の具体的な値ではなく、時間や空間が増加する「率」に焦点を当てることを示します。 +- 「時間資源と空間資源」は、それぞれ時間計算量(time complexity)空間計算量(space complexity)に対応します。 +- 「入力データ規模が増加するにつれて」とは、計算量がアルゴリズムの実行効率と入力データ規模との関係を反映していることを意味します。 +- 「時間と空間の増加傾向」とは、計算量解析が注目するのは実行時間や使用空間の具体的な値ではなく、時間や空間の増加の「速さ」であることを示します。 -**複雑度解析は実際のテスト方法の欠点を克服します**。これは以下の側面で反映されます: +**計算量解析は実測という方法の欠点を克服しています**。その点は次のように表れます。 -- 実際にコードを実行する必要がないため、より環境に優しく、エネルギー効率が良いです。 -- テスト環境に依存せず、すべての動作プラットフォームに適用できます。 -- 異なるデータ量でのアルゴリズムの効率を反映でき、特に大量データでのアルゴリズムの性能を示します。 +- 実際にコードを動かす必要がなく、より環境にやさしく省エネルギーです。 +- テスト環境から独立しており、解析結果はすべての実行プラットフォームに適用できます。 +- 異なるデータ量におけるアルゴリズム効率を表せ、とくに大規模データ量での性能を反映できます。 !!! tip - 複雑度の概念についてまだ混乱している場合でも、心配しないでください。以降の章で詳しく取り上げます。 + それでも計算量の概念がまだわかりにくくても、心配はいりません。後続の章で詳しく説明します。 -複雑度解析は、アルゴリズムの効率を評価する「ものさし」を提供し、実行に必要な時間と空間リソースを測定し、異なるアルゴリズムの効率を比較することを可能にします。 +計算量解析は、アルゴリズム効率を評価するための「物差し」を私たちに与えてくれます。これにより、あるアルゴリズムの実行に必要な時間資源と空間資源を測り、異なるアルゴリズム同士の効率を比較できます。 -複雑度は数学的概念であり、初心者には抽象的で困難かもしれません。この観点から、複雑度解析は最初に紹介するのに最も適したトピックではないかもしれません。しかし、特定のデータ構造やアルゴリズムの特性について議論するとき、その速度と空間使用量を分析することを避けるのは困難です。 +計算量は数学的な概念であり、初学者にとってはやや抽象的で、学習の難度も比較的高いかもしれません。この観点から見ると、計算量解析は最初に紹介する内容としてはあまり適していない可能性があります。しかし、あるデータ構造やアルゴリズムの特徴を議論する際には、その実行速度や空間使用状況の分析を避けることはできません。 -要約すると、データ構造とアルゴリズムに深く入る前に複雑度解析の基本的な理解を身につけることをお勧めします。**これにより、簡単なアルゴリズムで複雑度解析を実行できるようになります**。 +以上を踏まえると、データ構造とアルゴリズムを深く学ぶ前に、**まず計算量解析について初歩的な理解を持ち、簡単なアルゴリズムの計算量解析ができるようにしておくこと**を勧めます。 diff --git a/ja/docs/chapter_computational_complexity/space_complexity.md b/ja/docs/chapter_computational_complexity/space_complexity.md index c8a477051..8809b3e33 100644 --- a/ja/docs/chapter_computational_complexity/space_complexity.md +++ b/ja/docs/chapter_computational_complexity/space_complexity.md @@ -1,28 +1,28 @@ # 空間計算量 -空間計算量は、データ量が増加するにつれてアルゴリズムが占有するメモリ空間の増加傾向を測定するために使用されます。この概念は時間計算量と非常に似ていますが、「実行時間」が「占有メモリ空間」に置き換えられています。 +空間計算量(space complexity)は、アルゴリズムが占有するメモリ空間がデータ量の増加に伴ってどのように増えるかを測る指標です。この概念は時間計算量と非常によく似ており、「実行時間」を「占有メモリ空間」に置き換えるだけです。 ## アルゴリズムに関連する空間 -アルゴリズムが実行中に使用するメモリ空間には、主に以下の種類があります。 +アルゴリズムが実行中に使用するメモリ空間には、主に次の種類があります。 -- **入力空間**: アルゴリズムの入力データを格納するために使用されます。 -- **一時空間**: アルゴリズムの実行中に変数、オブジェクト、関数コンテキスト、その他のデータを格納するために使用されます。 -- **出力空間**: アルゴリズムの出力データを格納するために使用されます。 +- **入力空間**:アルゴリズムの入力データを格納するための空間。 +- **一時空間**:アルゴリズムの実行中に使用する変数、オブジェクト、関数コンテキストなどのデータを格納するための空間。 +- **出力空間**:アルゴリズムの出力データを格納するための空間。 -一般的に、空間計算量の統計範囲には「一時空間」と「出力空間」の両方が含まれます。 +一般に、空間計算量の集計範囲は「一時空間」と「出力空間」を合わせたものです。 -一時空間はさらに3つの部分に分けることができます。 +一時空間はさらに三つに分けられます。 -- **一時データ**: アルゴリズムの実行中に様々な定数、変数、オブジェクトなどを保存するために使用されます。 -- **スタックフレーム空間**: 呼び出された関数のコンテキストデータを保存するために使用されます。システムは関数が呼び出されるたびにスタックの頂上にスタックフレームを作成し、関数が返された後にスタックフレーム空間を解放します。 -- **命令空間**: コンパイル済みプログラム命令を格納するために使用され、実際の統計では通常無視できます。 +- **一時データ**:アルゴリズム実行中の各種定数、変数、オブジェクトなどを保存するための空間。 +- **スタックフレーム空間**:呼び出された関数のコンテキストデータを保存するための空間。システムは関数を呼び出すたびにスタックの先頭にスタックフレームを作成し、関数が戻るとその空間を解放します。 +- **命令空間**:コンパイル後のプログラム命令を保存するための空間で、実際の集計では通常無視されます。 -プログラムの空間計算量を分析する際、**通常は一時データ、スタックフレーム空間、出力データをカウントします**。以下の図に示されています。 +プログラムの空間計算量を分析する際には、**通常、一時データ、スタックフレーム空間、出力データの三つを数えます**。以下の図に示すとおりです。 -![Space types used in algorithms](space_complexity.assets/space_types.png) +![アルゴリズムで使用される関連空間](space_complexity.assets/space_types.png) -関連するコードは以下の通りです: +関連するコードを以下に示します。 === "Python" @@ -30,20 +30,20 @@ class Node: """クラス""" def __init__(self, x: int): - self.val: int = x # ノード値 - self.next: Node | None = None # 次のノードへの参照 + self.val: int = x # ノードの値 + self.next: Node | None = None # 次のノードへの参照 def function() -> int: """関数""" - # 特定の操作を実行... + # いくつかの処理を実行... return 0 - def algorithm(n) -> int: # 入力データ - A = 0 # 一時データ(定数、通常大文字) - b = 0 # 一時データ(変数) - node = Node(0) # 一時データ(オブジェクト) - c = function() # スタックフレーム空間(関数呼び出し) - return A + b + c # 出力データ + def algorithm(n) -> int: # 入力データ + A = 0 # 一時データ(定数。一般に大文字で表す) + b = 0 # 一時データ(変数) + node = Node(0) # 一時データ(オブジェクト) + c = function() # スタックフレーム空間(関数呼び出し) + return A + b + c # 出力データ ``` === "C++" @@ -58,16 +58,16 @@ /* 関数 */ int func() { - // 特定の操作を実行... + // いくつかの処理を実行... return 0; } - int algorithm(int n) { // 入力データ - const int a = 0; // 一時データ(定数) - int b = 0; // 一時データ(変数) - Node* node = new Node(0); // 一時データ(オブジェクト) - int c = func(); // スタックフレーム空間(関数呼び出し) - return a + b + c; // 出力データ + int algorithm(int n) { // 入力データ + const int a = 0; // 一時データ(定数) + int b = 0; // 一時データ(変数) + Node* node = new Node(0); // 一時データ(オブジェクト) + int c = func(); // スタックフレーム空間(関数呼び出し) + return a + b + c; // 出力データ } ``` @@ -80,19 +80,19 @@ Node next; Node(int x) { val = x; } } - + /* 関数 */ int function() { - // 特定の操作を実行... + // いくつかの処理を実行... return 0; } - - int algorithm(int n) { // 入力データ - final int a = 0; // 一時データ(定数) - int b = 0; // 一時データ(変数) - Node node = new Node(0); // 一時データ(オブジェクト) - int c = function(); // スタックフレーム空間(関数呼び出し) - return a + b + c; // 出力データ + + int algorithm(int n) { // 入力データ + final int a = 0; // 一時データ(定数) + int b = 0; // 一時データ(変数) + Node node = new Node(0); // 一時データ(オブジェクト) + int c = function(); // スタックフレーム空間(関数呼び出し) + return a + b + c; // 出力データ } ``` @@ -100,24 +100,23 @@ ```csharp title="" /* クラス */ - class Node { - int val; + class Node(int x) { + int val = x; Node next; - Node(int x) { val = x; } } /* 関数 */ int Function() { - // 特定の操作を実行... + // いくつかの処理を実行... return 0; } - int Algorithm(int n) { // 入力データ - const int a = 0; // 一時データ(定数) - int b = 0; // 一時データ(変数) - Node node = new(0); // 一時データ(オブジェクト) - int c = Function(); // スタックフレーム空間(関数呼び出し) - return a + b + c; // 出力データ + int Algorithm(int n) { // 入力データ + const int a = 0; // 一時データ(定数) + int b = 0; // 一時データ(変数) + Node node = new(0); // 一時データ(オブジェクト) + int c = Function(); // スタックフレーム空間(関数呼び出し) + return a + b + c; // 出力データ } ``` @@ -130,14 +129,14 @@ next *node } - /* ノード構造体を作成 */ + /* node 構造体を作成 */ func newNode(val int) *node { return &node{val: val} } - + /* 関数 */ func function() int { - // 特定の操作を実行... + // いくつかの処理を実行... return 0 } @@ -165,16 +164,16 @@ /* 関数 */ func function() -> Int { - // 特定の操作を実行... + // いくつかの処理を実行... return 0 } func algorithm(n: Int) -> Int { // 入力データ - let a = 0 // 一時データ(定数) - var b = 0 // 一時データ(変数) - let node = Node(x: 0) // 一時データ(オブジェクト) - let c = function() // スタックフレーム空間(関数呼び出し) - return a + b + c // 出力データ + let a = 0 // 一時データ(定数) + var b = 0 // 一時データ(変数) + let node = Node(x: 0) // 一時データ(オブジェクト) + let c = function() // スタックフレーム空間(関数呼び出し) + return a + b + c // 出力データ } ``` @@ -186,23 +185,23 @@ val; next; constructor(val) { - this.val = val === undefined ? 0 : val; // ノード値 + this.val = val === undefined ? 0 : val; // ノードの値 this.next = null; // 次のノードへの参照 } } /* 関数 */ function constFunc() { - // 特定の操作を実行 + // いくつかの処理を実行 return 0; } - function algorithm(n) { // 入力データ - const a = 0; // 一時データ(定数) - let b = 0; // 一時データ(変数) - const node = new Node(0); // 一時データ(オブジェクト) - const c = constFunc(); // スタックフレーム空間(関数呼び出し) - return a + b + c; // 出力データ + function algorithm(n) { // 入力データ + const a = 0; // 一時データ(定数) + let b = 0; // 一時データ(変数) + const node = new Node(0); // 一時データ(オブジェクト) + const c = constFunc(); // スタックフレーム空間(関数呼び出し) + return a + b + c; // 出力データ } ``` @@ -214,14 +213,14 @@ val: number; next: Node | null; constructor(val?: number) { - this.val = val === undefined ? 0 : val; // ノード値 + this.val = val === undefined ? 0 : val; // ノードの値 this.next = null; // 次のノードへの参照 } } /* 関数 */ function constFunc(): number { - // 特定の操作を実行 + // いくつかの処理を実行 return 0; } @@ -246,7 +245,7 @@ /* 関数 */ int function() { - // 特定の操作を実行... + // いくつかの処理を実行... return 0; } @@ -264,14 +263,14 @@ ```rust title="" use std::rc::Rc; use std::cell::RefCell; - + /* 構造体 */ struct Node { val: i32, next: Option>>, } - /* コンストラクタ */ + /* Node 構造体を作成 */ impl Node { fn new(val: i32) -> Self { Self { val: val, next: None } @@ -279,17 +278,17 @@ } /* 関数 */ - fn function() -> i32 { - // 特定の操作を実行... + fn function() -> i32 { + // いくつかの処理を実行... return 0; } - fn algorithm(n: i32) -> i32 { // 入力データ - const a: i32 = 0; // 一時データ(定数) - let mut b = 0; // 一時データ(変数) - let node = Node::new(0); // 一時データ(オブジェクト) - let c = function(); // スタックフレーム空間(関数呼び出し) - return a + b + c; // 出力データ + fn algorithm(n: i32) -> i32 { // 入力データ + const a: i32 = 0; // 一時データ(定数) + let mut b = 0; // 一時データ(変数) + let node = Node::new(0); // 一時データ(オブジェクト) + let c = function(); // スタックフレーム空間(関数呼び出し) + return a + b + c; // 出力データ } ``` @@ -298,34 +297,80 @@ ```c title="" /* 関数 */ int func() { - // 特定の操作を実行... + // いくつかの処理を実行... return 0; } - int algorithm(int n) { // 入力データ - const int a = 0; // 一時データ(定数) - int b = 0; // 一時データ(変数) - int c = func(); // スタックフレーム空間(関数呼び出し) - return a + b + c; // 出力データ + int algorithm(int n) { // 入力データ + const int a = 0; // 一時データ(定数) + int b = 0; // 一時データ(変数) + int c = func(); // スタックフレーム空間(関数呼び出し) + return a + b + c; // 出力データ } ``` === "Kotlin" ```kotlin title="" + /* クラス */ + class Node(var _val: Int) { + var next: Node? = null + } + /* 関数 */ + fun function(): Int { + // いくつかの処理を実行... + return 0 + } + + fun algorithm(n: Int): Int { // 入力データ + val a = 0 // 一時データ(定数) + var b = 0 // 一時データ(変数) + val node = Node(0) // 一時データ(オブジェクト) + val c = function() // スタックフレーム空間(関数呼び出し) + return a + b + c // 出力データ + } ``` -## 計算方法 +=== "Ruby" -空間計算量を計算する方法は時間計算量とほぼ同様で、統計対象を「操作数」から「使用空間のサイズ」に変更するだけです。 + ```ruby title="" + ### クラス ### + class Node + attr_accessor :val # ノードの値 + attr_accessor :next # 次のノードへの参照 -しかし、時間計算量とは異なり、**通常は最悪ケース空間計算量のみに焦点を当てます**。これは、メモリ空間がハード要件であり、すべての入力データの下で十分なメモリ空間が確保されていることを保証する必要があるためです。 + def initialize(x) + @val = x + end + end -以下のコードを考えてみましょう。最悪ケース空間計算量の「最悪ケース」という用語には2つの意味があります。 + ### 関数 ### + def function + # いくつかの処理を実行... + 0 + end -1. **最悪の入力データに基づく**: $n < 10$の場合、空間計算量は$O(1)$ですが、$n > 10$の場合、初期化された配列`nums`が$O(n)$の空間を占有するため、最悪ケース空間計算量は$O(n)$です。 -2. **アルゴリズムの実行中に使用されるピークメモリに基づく**: 例えば、最後の行を実行する前、プログラムは$O(1)$の空間を占有します。配列`nums`を初期化する際、プログラムは$O(n)$の空間を占有するため、最悪ケース空間計算量は$O(n)$です。 + ### アルゴリズム ### + def algorithm(n) # 入力データ + a = 0 # 一時データ(定数) + b = 0 # 一時データ(変数) + node = Node.new(0) # 一時データ(オブジェクト) + c = function # スタックフレーム空間(関数呼び出し) + a + b + c # 出力データ + end + ``` + +## 推定方法 + +空間計算量の推定方法は時間計算量とおおむね同じで、数える対象を「操作回数」から「使用空間の大きさ」に変えるだけです。 + +ただし時間計算量と異なり、**通常は最悪空間計算量だけに注目します**。メモリ空間は厳格な要件であり、どの入力データに対しても十分なメモリを確保できることを保証しなければならないからです。 + +以下のコードを見ると、最悪空間計算量における「最悪」には二つの意味があります。 + +1. **最悪の入力データを基準にする**:$n < 10$ のとき空間計算量は $O(1)$ ですが、$n > 10$ のとき初期化される配列 `nums` が $O(n)$ の空間を占有するため、最悪空間計算量は $O(n)$ です。 +2. **アルゴリズム実行中のメモリ使用量のピークを基準にする**:例えば、プログラムは最後の行を実行する前までは $O(1)$ の空間しか使いませんが、配列 `nums` を初期化するときには $O(n)$ の空間を占有するため、最悪空間計算量は $O(n)$ です。 === "Python" @@ -437,10 +482,10 @@ ```rust title="" fn algorithm(n: i32) { - let a = 0; // O(1) - let b = [0; 10000]; // O(1) + let a = 0; // O(1) + let b = [0; 10000]; // O(1) if n > 10 { - let nums = vec![0; n as usize]; // O(n) + let nums = vec![0; n as usize]; // O(n) } } ``` @@ -459,25 +504,41 @@ === "Kotlin" ```kotlin title="" - + fun algorithm(n: Int) { + val a = 0 // O(1) + val b = IntArray(10000) // O(1) + if (n > 10) { + val nums = IntArray(n) // O(n) + } + } ``` -**再帰関数では、スタックフレーム空間を考慮に入れる必要があります**。以下のコードを考えてみましょう: +=== "Ruby" + + ```ruby title="" + def algorithm(n) + a = 0 # O(1) + b = Array.new(10000) # O(1) + nums = Array.new(n) if n > 10 # O(n) + end + ``` + +**再帰関数では、スタックフレーム空間の集計に注意が必要です**。以下のコードを見てみましょう。 === "Python" ```python title="" def function() -> int: - # 特定の操作を実行 + # いくつかの処理を実行 return 0 def loop(n: int): - """ループ O(1)""" + """ループの空間計算量は O(1)""" for _ in range(n): function() def recur(n: int): - """再帰 O(n)""" + """再帰の空間計算量は O(n)""" if n == 1: return return recur(n - 1) @@ -487,16 +548,16 @@ ```cpp title="" int func() { - // 特定の操作を実行 + // いくつかの処理を実行 return 0; } - /* サイクル O(1) */ + /* ループの空間計算量は O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { func(); } } - /* 再帰 O(n) */ + /* 再帰の空間計算量は O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); @@ -507,16 +568,16 @@ ```java title="" int function() { - // 特定の操作を実行 + // いくつかの処理を実行 return 0; } - /* サイクル O(1) */ + /* ループの空間計算量は O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { function(); } } - /* 再帰 O(n) */ + /* 再帰の空間計算量は O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); @@ -527,16 +588,16 @@ ```csharp title="" int Function() { - // 特定の操作を実行 + // いくつかの処理を実行 return 0; } - /* サイクル O(1) */ + /* ループの空間計算量は O(1) */ void Loop(int n) { for (int i = 0; i < n; i++) { Function(); } } - /* 再帰 O(n) */ + /* 再帰の空間計算量は O(n) */ int Recur(int n) { if (n == 1) return 1; return Recur(n - 1); @@ -547,18 +608,18 @@ ```go title="" func function() int { - // 特定の操作を実行 + // いくつかの処理を実行 return 0 } - - /* サイクル O(1) */ + + /* ループの空間計算量は O(1) */ func loop(n int) { for i := 0; i < n; i++ { function() } } - - /* 再帰 O(n) */ + + /* 再帰の空間計算量は O(n) */ func recur(n int) { if n == 1 { return @@ -572,18 +633,18 @@ ```swift title="" @discardableResult func function() -> Int { - // 特定の操作を実行 + // いくつかの処理を実行 return 0 } - /* サイクル O(1) */ + /* ループの空間計算量は O(1) */ func loop(n: Int) { for _ in 0 ..< n { function() } } - /* 再帰 O(n) */ + /* 再帰の空間計算量は O(n) */ func recur(n: Int) { if n == 1 { return @@ -596,16 +657,16 @@ ```javascript title="" function constFunc() { - // 特定の操作を実行 + // いくつかの処理を実行 return 0; } - /* サイクル O(1) */ + /* ループの空間計算量は O(1) */ function loop(n) { for (let i = 0; i < n; i++) { constFunc(); } } - /* 再帰 O(n) */ + /* 再帰の空間計算量は O(n) */ function recur(n) { if (n === 1) return; return recur(n - 1); @@ -616,16 +677,16 @@ ```typescript title="" function constFunc(): number { - // 特定の操作を実行 + // いくつかの処理を実行 return 0; } - /* サイクル O(1) */ + /* ループの空間計算量は O(1) */ function loop(n: number): void { for (let i = 0; i < n; i++) { constFunc(); } } - /* 再帰 O(n) */ + /* 再帰の空間計算量は O(n) */ function recur(n: number): void { if (n === 1) return; return recur(n - 1); @@ -636,16 +697,16 @@ ```dart title="" int function() { - // 特定の操作を実行 + // いくつかの処理を実行 return 0; } - /* サイクル O(1) */ + /* ループの空間計算量は O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { function(); } } - /* 再帰 O(n) */ + /* 再帰の空間計算量は O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); @@ -656,17 +717,17 @@ ```rust title="" fn function() -> i32 { - // 特定の操作を実行 + // いくつかの処理を実行 return 0; } - /* サイクル O(1) */ + /* ループの空間計算量は O(1) */ fn loop(n: i32) { for i in 0..n { function(); } } - /* 再帰 O(n) */ - void recur(n: i32) { + /* 再帰の空間計算量は O(n) */ + fn recur(n: i32) { if n == 1 { return; } @@ -678,16 +739,16 @@ ```c title="" int func() { - // 特定の操作を実行 + // いくつかの処理を実行 return 0; } - /* サイクル O(1) */ + /* ループの空間計算量は O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { func(); } } - /* 再帰 O(n) */ + /* 再帰の空間計算量は O(n) */ void recur(int n) { if (n == 1) return; recur(n - 1); @@ -697,89 +758,123 @@ === "Kotlin" ```kotlin title="" - + fun function(): Int { + // いくつかの処理を実行 + return 0 + } + /* ループの空間計算量は O(1) */ + fun loop(n: Int) { + for (i in 0..関数は独立して実行でき、すべてのパラメータが明示的に渡されます。メソッドはオブジェクトに関連付けられ、それを呼び出すオブジェクトに暗黙的に渡され、クラスのインスタンス内に含まれるデータを操作できます。 +関数(function)は独立して実行でき、すべての引数は明示的に渡されます。メソッド(method)はオブジェクトに関連付けられ、それを呼び出すオブジェクトが暗黙的に渡され、クラスのインスタンスに含まれるデータを操作できます。 -一般的なプログラミング言語からの例をいくつか示します: +以下では、いくつかの一般的なプログラミング言語を例に説明します。 -- Cは手続き型プログラミング言語で、オブジェクト指向の概念がないため、関数のみがあります。しかし、構造体(struct)を作成することでオブジェクト指向プログラミングをシミュレートでき、これらの構造体に関連付けられた関数は他のプログラミング言語のメソッドと同等です。 -- JavaとC#はオブジェクト指向プログラミング言語で、コードブロック(メソッド)は通常クラスの一部です。静的メソッドはクラスにバインドされ、特定のインスタンス変数にアクセスできないため、関数のように動作します。 -- C++とPythonは手続き型プログラミング(関数)とオブジェクト指向プログラミング(メソッド)の両方をサポートしています。 +- C 言語は手続き型プログラミング言語であり、オブジェクト指向の概念がないため、関数しかありません。ただし、構造体(struct)を作成してオブジェクト指向プログラミングを模倣でき、構造体に関連付けられた関数は、他のプログラミング言語におけるメソッドに相当します。 +- Java と C# はオブジェクト指向のプログラミング言語であり、コードブロック(メソッド)は通常あるクラスの一部です。静的メソッドの振る舞いは関数に似ており、クラスに束縛され、特定のインスタンス変数にはアクセスできません。 +- C++ と Python は、手続き型プログラミング(関数)にもオブジェクト指向プログラミング(メソッド)にも対応しています。 -**Q**: 「空間計算量の一般的な種類」の図は、占有空間の絶対サイズを反映していますか? +**Q**:「一般的な空間計算量の種類」の図が表しているのは、使用空間の絶対量ですか? -いいえ、図は空間計算量を示しており、これは増加傾向を反映するものであり、占有空間の絶対サイズではありません。 +いいえ。この図が示しているのは空間計算量であり、表しているのは増加傾向であって、使用空間の絶対量ではありません。 -$n = 8$を取ると、各曲線の値がその関数に対応していないことに気づくかもしれません。これは、各曲線に定数項が含まれているためで、値の範囲を視覚的に快適な範囲に圧縮することを意図しています。 +$n = 8$ と仮定すると、各曲線の値が対応する関数と一致していないように見えるかもしれません。これは、各曲線に定数項が含まれており、値の範囲を視覚的に見やすい範囲へ圧縮しているためです。 -実際には、通常は各メソッドの「定数項」複雑度を知らないため、複雑度のみに基づいて$n = 8$の最良ソリューションを選択することは一般的に不可能です。しかし、$n = 8^5$の場合、増加傾向が支配的になるため、選択がはるかに容易になります。 +実際には、各手法の「定数項」の複雑度がどれほどか通常は分からないため、一般に複雑度だけを根拠に $n = 8$ 以下で最適解を選ぶことはできません。ただし、$n = 8^5$ であれば選びやすく、このときは増加傾向がすでに支配的になっています。 + +**Q** 実際の利用場面に応じて、時間(または空間)を犠牲にしてアルゴリズムを設計することはありますか? + +実際の応用では、多くの場合、空間を犠牲にして時間を得る選択をします。たとえばデータベースのインデックスでは、通常 B+ 木やハッシュインデックスを構築し、大量のメモリ空間を使う代わりに、$O(\log n)$ あるいは $O(1)$ の高速な検索を実現します。 + +空間資源が貴重な場面では、時間を犠牲にして空間を得ることもあります。たとえば組み込み開発では、デバイスのメモリが非常に貴重なため、エンジニアはハッシュテーブルの使用をやめ、配列による順次探索を選んでメモリ使用量を節約することがあります。その代償として探索は遅くなります。 diff --git a/ja/docs/chapter_computational_complexity/time_complexity.md b/ja/docs/chapter_computational_complexity/time_complexity.md index 960857cb2..126a3eccb 100644 --- a/ja/docs/chapter_computational_complexity/time_complexity.md +++ b/ja/docs/chapter_computational_complexity/time_complexity.md @@ -1,22 +1,22 @@ # 時間計算量 -実行時間は、アルゴリズムの効率を直感的に評価できます。アルゴリズムの実行時間を正確に推定するにはどうすればよいでしょうか? +実行時間はアルゴリズムの効率を直感的かつ正確に反映します。あるコードの実行時間を正確に見積もりたい場合、どのようにすればよいでしょうか? -1. **実行プラットフォームの決定**: これには、ハードウェア構成、プログラミング言語、システム環境などが含まれ、これらすべてがコードの実行効率に影響する可能性があります。 -2. **様々な計算操作の実行時間の評価**: 例えば、加算操作`+`は1 ns、乗算操作`*`は10 ns、印刷操作`print()`は5 nsなどかかる可能性があります。 -3. **コード内のすべての計算操作をカウント**: これらすべての操作の実行時間を合計すると、総実行時間が得られます。 +1. **実行プラットフォームを特定する**。ハードウェア構成、プログラミング言語、システム環境などが含まれ、これらの要因はいずれもコードの実行効率に影響します。 +2. **各種計算操作に必要な実行時間を評価する**。例えば加算 `+` には 1 ns 、乗算 `*` には 10 ns 、出力 `print()` には 5 ns などが必要です。 +3. **コード中のすべての計算操作を数える**。そして各操作の実行時間を合計することで、実行時間を得ます。 -例えば、入力サイズが$n$の以下のコードを考えてみましょう: +例えば次のコードでは、入力データサイズを $n$ とします: === "Python" ```python title="" - # 特定の操作プラットフォーム下で + # ある実行プラットフォーム上で def algorithm(n: int): a = 2 # 1 ns a = a + 1 # 1 ns a = a * 2 # 10 ns - # n回ループ + # n 回ループ for _ in range(n): # 1 ns print(0) # 5 ns ``` @@ -24,13 +24,13 @@ === "C++" ```cpp title="" - // 特定の操作プラットフォーム下で + // ある実行プラットフォーム上で void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns - // n回ループ - for (int i = 0; i < n; i++) { // 1 ns, 毎回i++が実行される + // n 回ループ + for (int i = 0; i < n; i++) { // 1 ns cout << 0 << endl; // 5 ns } } @@ -39,13 +39,13 @@ === "Java" ```java title="" - // 特定の操作プラットフォーム下で + // ある実行プラットフォーム上で void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns - // n回ループ - for (int i = 0; i < n; i++) { // 1 ns, 毎回i++が実行される + // n 回ループ + for (int i = 0; i < n; i++) { // 1 ns System.out.println(0); // 5 ns } } @@ -54,13 +54,13 @@ === "C#" ```csharp title="" - // 特定の操作プラットフォーム下で + // ある実行プラットフォーム上で void Algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns - // n回ループ - for (int i = 0; i < n; i++) { // 1 ns, 毎回i++が実行される + // n 回ループ + for (int i = 0; i < n; i++) { // 1 ns Console.WriteLine(0); // 5 ns } } @@ -69,12 +69,12 @@ === "Go" ```go title="" - // 特定の操作プラットフォーム下で + // ある実行プラットフォーム上で func algorithm(n int) { a := 2 // 1 ns a = a + 1 // 1 ns a = a * 2 // 10 ns - // n回ループ + // n 回ループ for i := 0; i < n; i++ { // 1 ns fmt.Println(a) // 5 ns } @@ -84,12 +84,12 @@ === "Swift" ```swift title="" - // 特定の操作プラットフォーム下で + // ある実行プラットフォーム上で func algorithm(n: Int) { var a = 2 // 1 ns a = a + 1 // 1 ns a = a * 2 // 10 ns - // n回ループ + // n 回ループ for _ in 0 ..< n { // 1 ns print(0) // 5 ns } @@ -99,13 +99,13 @@ === "JS" ```javascript title="" - // 特定の操作プラットフォーム下で + // ある実行プラットフォーム上で function algorithm(n) { var a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns - // n回ループ - for(let i = 0; i < n; i++) { // 1 ns, 毎回i++が実行される + // n 回ループ + for(let i = 0; i < n; i++) { // 1 ns console.log(0); // 5 ns } } @@ -114,13 +114,13 @@ === "TS" ```typescript title="" - // 特定の操作プラットフォーム下で + // ある実行プラットフォーム上で function algorithm(n: number): void { var a: number = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns - // n回ループ - for(let i = 0; i < n; i++) { // 1 ns, 毎回i++が実行される + // n 回ループ + for(let i = 0; i < n; i++) { // 1 ns console.log(0); // 5 ns } } @@ -129,13 +129,13 @@ === "Dart" ```dart title="" - // 特定の操作プラットフォーム下で + // ある実行プラットフォーム上で void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns - // n回ループ - for (int i = 0; i < n; i++) { // 1 ns, 毎回i++が実行される + // n 回ループ + for (int i = 0; i < n; i++) { // 1 ns print(0); // 5 ns } } @@ -144,13 +144,13 @@ === "Rust" ```rust title="" - // 特定の操作プラットフォーム下で + // ある実行プラットフォーム上で fn algorithm(n: i32) { let mut a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns - // n回ループ - for _ in 0..n { // 毎回i++で1 ns + // n 回ループ + for _ in 0..n { // 1 ns println!("{}", 0); // 5 ns } } @@ -159,13 +159,13 @@ === "C" ```c title="" - // 特定の操作プラットフォーム下で + // ある実行プラットフォーム上で void algorithm(int n) { int a = 2; // 1 ns a = a + 1; // 1 ns a = a * 2; // 10 ns - // n回ループ - for (int i = 0; i < n; i++) { // 1 ns, 毎回i++が実行される + // n 回ループ + for (int i = 0; i < n; i++) { // 1 ns printf("%d", 0); // 5 ns } } @@ -174,34 +174,58 @@ === "Kotlin" ```kotlin title="" - + // ある実行プラットフォーム上で + fun algorithm(n: Int) { + var a = 2 // 1 ns + a = a + 1 // 1 ns + a = a * 2 // 10 ns + // n 回ループ + for (i in 0.. 1$の時はアルゴリズム`A`より遅く、$n > 1,000,000$の時は`C`より遅くなります。実際、入力データサイズ$n$が十分に大きい限り、「定数オーダー」複雑度アルゴリズムは常に「線形オーダー」よりも優れており、時間増加傾向の本質を示しています。 -- **時間計算量分析はより直感的です**。明らかに、実行プラットフォームと計算操作の種類は実行時間増加の傾向とは無関係です。したがって、時間計算量分析では、すべての計算操作の実行時間を同じ「単位時間」として扱うことができ、「計算操作実行時間カウント」を「計算操作カウント」に単純化できます。これにより推定の複雑さが大幅に軽減されます。 -- **時間計算量には制限があります**。例えば、アルゴリズム`A`と`C`は同じ時間計算量を持ちますが、実際の実行時間は大きく異なる場合があります。同様に、アルゴリズム`B`は`C`よりも高い時間計算量を持ちますが、入力データサイズ$n$が小さい場合は明らかに優れています。これらの場合、時間計算量のみに基づいてアルゴリズムの効率を判断することは困難です。しかし、これらの問題にもかかわらず、複雑度分析はアルゴリズムの効率を評価するための最も効果的で一般的に使用される方法です。 +以下の図は、上記 3 つのアルゴリズム関数の時間計算量を示しています。 -## 漸近上限 +- アルゴリズム `A` には出力操作が $1$ 回しかなく、実行時間は $n$ が大きくなっても増加しません。このアルゴリズムの時間計算量を「定数階」と呼びます。 +- アルゴリズム `B` の出力操作は $n$ 回ループする必要があり、実行時間は $n$ の増加に対して線形に増加します。このアルゴリズムの時間計算量は「線形階」と呼ばれます。 +- アルゴリズム `C` の出力操作は $1000000$ 回ループする必要があり、実行時間は長いものの、入力データサイズ $n$ とは無関係です。したがって `C` の時間計算量は `A` と同じく、依然として「定数階」です。 -入力サイズが$n$の関数を考えてみましょう: +![アルゴリズム A、B、C の時間増加傾向](time_complexity.assets/time_complexity_simple_example.png) + +アルゴリズムの実行時間を直接数える方法と比べて、時間計算量分析にはどのような特徴があるでしょうか? + +- **時間計算量はアルゴリズム効率を有効に評価できます**。例えばアルゴリズム `B` の実行時間は線形に増加するため、$n > 1$ ではアルゴリズム `A` より遅く、$n > 1000000$ ではアルゴリズム `C` より遅くなります。実際、入力データサイズ $n$ が十分に大きければ、「定数階」のアルゴリズムは必ず「線形階」のアルゴリズムより優れます。これが実行時間の増加傾向の意味です。 +- **時間計算量の見積もり方法はより簡潔です**。実行プラットフォームや計算操作の種類は、アルゴリズム実行時間の増加傾向とは無関係です。そのため時間計算量分析では、すべての計算操作の実行時間を同じ「単位時間」とみなしてよく、「計算操作の実行時間を数える」作業を「計算操作の個数を数える」作業へ簡略化できます。これにより見積もりの難易度は大きく下がります。 +- **時間計算量には一定の限界もあります**。例えばアルゴリズム `A` と `C` の時間計算量は同じでも、実際の実行時間には大きな差があります。同様に、アルゴリズム `B` の時間計算量は `C` より高いものの、入力データサイズ $n$ が小さい場合にはアルゴリズム `B` のほうが明らかに優れます。このような場合、時間計算量だけでアルゴリズム効率の高低を判断するのは難しいことがあります。もっとも、こうした問題があっても、複雑度分析は依然としてアルゴリズム効率を評価する最も有効で一般的な方法です。 + +## 関数の漸近上界 + +入力サイズが $n$ の次の関数を考えます: === "Python" @@ -451,7 +509,7 @@ $$ a = 1 # +1 a = a + 1 # +1 a = a * 2 # +1 - # n回ループ + # n 回ループ for i in range(n): # +1 print(0) # +1 ``` @@ -463,8 +521,8 @@ $$ int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 - // n回ループ - for (int i = 0; i < n; i++) { // +1 (毎回i++が実行される) + // n 回ループ + for (int i = 0; i < n; i++) { // +1(各反復で i ++ を実行) cout << 0 << endl; // +1 } } @@ -477,8 +535,8 @@ $$ int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 - // n回ループ - for (int i = 0; i < n; i++) { // +1 (毎回i++が実行される) + // n 回ループ + for (int i = 0; i < n; i++) { // +1(各反復で i ++ を実行) System.out.println(0); // +1 } } @@ -491,8 +549,8 @@ $$ int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 - // n回ループ - for (int i = 0; i < n; i++) { // +1 (毎回i++が実行される) + // n 回ループ + for (int i = 0; i < n; i++) { // +1(各反復で i ++ を実行) Console.WriteLine(0); // +1 } } @@ -505,7 +563,7 @@ $$ a := 1 // +1 a = a + 1 // +1 a = a * 2 // +1 - // n回ループ + // n 回ループ for i := 0; i < n; i++ { // +1 fmt.Println(a) // +1 } @@ -519,7 +577,7 @@ $$ var a = 1 // +1 a = a + 1 // +1 a = a * 2 // +1 - // n回ループ + // n 回ループ for _ in 0 ..< n { // +1 print(0) // +1 } @@ -533,8 +591,8 @@ $$ var a = 1; // +1 a += 1; // +1 a *= 2; // +1 - // n回ループ - for(let i = 0; i < n; i++){ // +1 (毎回i++が実行される) + // n 回ループ + for(let i = 0; i < n; i++){ // +1(各反復で i ++ を実行) console.log(0); // +1 } } @@ -547,8 +605,8 @@ $$ var a: number = 1; // +1 a += 1; // +1 a *= 2; // +1 - // n回ループ - for(let i = 0; i < n; i++){ // +1 (毎回i++が実行される) + // n 回ループ + for(let i = 0; i < n; i++){ // +1(各反復で i ++ を実行) console.log(0); // +1 } } @@ -561,8 +619,8 @@ $$ int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 - // n回ループ - for (int i = 0; i < n; i++) { // +1 (毎回i++が実行される) + // n 回ループ + for (int i = 0; i < n; i++) { // +1(各反復で i ++ を実行) print(0); // +1 } } @@ -576,8 +634,8 @@ $$ a = a + 1; // +1 a = a * 2; // +1 - // n回ループ - for _ in 0..n { // +1 (毎回i++が実行される) + // n 回ループ + for _ in 0..n { // +1(各反復で i ++ を実行) println!("{}", 0); // +1 } } @@ -590,8 +648,8 @@ $$ int a = 1; // +1 a = a + 1; // +1 a = a * 2; // +1 - // n回ループ - for (int i = 0; i < n; i++) { // +1 (毎回i++が実行される) + // n 回ループ + for (int i = 0; i < n; i++) { // +1(各反復で i ++ を実行) printf("%d", 0); // +1 } } @@ -600,53 +658,77 @@ $$ === "Kotlin" ```kotlin title="" - + fun algorithm(n: Int) { + var a = 1 // +1 + a = a + 1 // +1 + a = a * 2 // +1 + // n 回ループ + for (i in 0..ビッグO記法として知られ、関数$T(n)$の漸近上限を表します。 +$T(n)$ は一次関数であり、実行時間の増加傾向が線形であることを示しています。したがってその時間計算量は線形階です。 -本質的に、時間計算量分析は「操作数$T(n)$」の漸近上限を見つけることです。それには正確な数学的定義があります。 +線形階の時間計算量を $O(n)$ と表します。この数学記号はビッグ $O$ 記法(big-$O$ notation)と呼ばれ、関数 $T(n)$ の漸近上界(asymptotic upper bound)を表します。 -!!! note "漸近上限" +時間計算量の分析は本質的に「操作回数 $T(n)$」の漸近上界を求めることであり、明確な数学的定義があります。 - すべての$n > n_0$に対して$T(n) \leq c \cdot f(n)$となるような正の実数$c$と$n_0$が存在する場合、$f(n)$は$T(n)$の漸近上限とみなされ、$T(n) = O(f(n))$と表記されます。 +!!! note "関数の漸近上界" -下図に示されているように、漸近上限の計算では、$n$が無限大に近づくにつれて、$T(n)$と$f(n)$が同じ増加オーダーを持ち、定数因子$c$のみが異なるような関数$f(n)$を見つけることが含まれます。 + 正の実数 $c$ と実数 $n_0$ が存在し、すべての $n > n_0$ について $T(n) \leq c \cdot f(n)$ が成り立つならば、$f(n)$ は $T(n)$ の漸近上界の 1 つであるとみなせます。これを $T(n) = O(f(n))$ と記します。 -![Asymptotic upper bound of a function](time_complexity.assets/asymptotic_upper_bound.png) +下図のように、漸近上界を求めるとは関数 $f(n)$ を探すことであり、$n$ が無限大へ近づくときに $T(n)$ と $f(n)$ が同じ増加オーダーにあり、定数係数 $c$ だけが異なる状態を表します。 -## 計算方法 +![関数の漸近上界](time_complexity.assets/asymptotic_upper_bound.png) -漸近上限の概念は数学的に濃密に見えるかもしれませんが、今すぐ完全に理解する必要はありません。まず計算方法を理解し、時間をかけて練習し理解しましょう。 +## 求め方 -$f(n)$が決まれば、時間計算量$O(f(n))$が得られます。しかし、漸近上限$f(n)$をどのように決定するのでしょうか?このプロセスには一般的に2つのステップが含まれます:操作数のカウントと漸近上限の決定です。 +漸近上界はやや数学色が強い概念ですが、完全に理解できていなくても心配はいりません。まずは求め方を押さえ、実践を重ねる中で徐々にその数学的意味をつかめば十分です。 -### ステップ1: 操作数のカウント +定義より、$f(n)$ が定まれば時間計算量 $O(f(n))$ が得られます。では、漸近上界 $f(n)$ をどのように決めればよいのでしょうか。大きく 2 段階あります。まず操作回数を数え、その後で漸近上界を判断します。 -このステップでは、コードを行ごとに確認します。しかし、$c \cdot f(n)$の定数$c$の存在により、**$T(n)$のすべての係数と定数項は無視できます**。この原理により、操作をカウントする際の簡略化技法が可能になります。 +### 第 1 ステップ:操作回数を数える -1. **$T(n)$の定数項を無視します**。これらは$n$とは無関係であるため、時間計算量に影響しません。 -2. **すべての係数を省略します**。例えば、$2n$、$5n + 1$回などのループは、$n$の前の係数が時間計算量に影響しないため、$n$回に簡略化できます。 -3. **ネストしたループには乗算を使用します**。総操作数は各ループの操作数の積であり、ポイント1と2の簡略化技法を各ループレベルに適用します。 +コードについては、上から下へ 1 行ずつ数えれば十分です。しかし、前述の $c \cdot f(n)$ における定数係数 $c$ は任意に大きく取れるため、**操作回数 $T(n)$ に含まれるさまざまな係数や定数項は無視できます**。この原則から、次のような簡略化のコツが得られます。 -関数が与えられた場合、これらの技法を使用して操作をカウントできます: +1. **$T(n)$ 中の定数を無視する**。それらはすべて $n$ と無関係なので、時間計算量には影響しません。 +2. **すべての係数を省略する**。例えば $2n$ 回や $5n + 1$ 回のループは、いずれも $n$ 回と簡略化できます。$n$ の前の係数は時間計算量に影響しないからです。 +3. **ループが入れ子のときは乗算を使う**。総操作回数は外側のループと内側のループの操作回数の積に等しく、各ループ層には引き続き `1.` と `2.` のコツをそれぞれ適用できます。 + +次の関数では、上記のコツを使って操作回数を数えられます: === "Python" ```python title="" def algorithm(n: int): - a = 1 # +0 (技法1) - a = a + n # +0 (技法1) - # +n (技法2) + a = 1 # +0(コツ 1) + a = a + n # +0(コツ 1) + # +n(コツ 2) for i in range(5 * n + 1): print(0) - # +n*n (技法3) + # +n*n(コツ 3) for i in range(2 * n): for j in range(n + 1): print(0) @@ -656,13 +738,13 @@ $f(n)$が決まれば、時間計算量$O(f(n))$が得られます。しかし ```cpp title="" void algorithm(int n) { - int a = 1; // +0 (技法1) - a = a + n; // +0 (技法1) - // +n (技法2) + int a = 1; // +0(コツ 1) + a = a + n; // +0(コツ 1) + // +n(コツ 2) for (int i = 0; i < 5 * n + 1; i++) { cout << 0 << endl; } - // +n*n (技法3) + // +n*n(コツ 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { cout << 0 << endl; @@ -675,13 +757,13 @@ $f(n)$が決まれば、時間計算量$O(f(n))$が得られます。しかし ```java title="" void algorithm(int n) { - int a = 1; // +0 (技法1) - a = a + n; // +0 (技法1) - // +n (技法2) + int a = 1; // +0(コツ 1) + a = a + n; // +0(コツ 1) + // +n(コツ 2) for (int i = 0; i < 5 * n + 1; i++) { System.out.println(0); } - // +n*n (技法3) + // +n*n(コツ 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { System.out.println(0); @@ -694,13 +776,13 @@ $f(n)$が決まれば、時間計算量$O(f(n))$が得られます。しかし ```csharp title="" void Algorithm(int n) { - int a = 1; // +0 (技法1) - a = a + n; // +0 (技法1) - // +n (技法2) + int a = 1; // +0(コツ 1) + a = a + n; // +0(コツ 1) + // +n(コツ 2) for (int i = 0; i < 5 * n + 1; i++) { Console.WriteLine(0); } - // +n*n (技法3) + // +n*n(コツ 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { Console.WriteLine(0); @@ -713,13 +795,13 @@ $f(n)$が決まれば、時間計算量$O(f(n))$が得られます。しかし ```go title="" func algorithm(n int) { - a := 1 // +0 (技法1) - a = a + n // +0 (技法1) - // +n (技法2) + a := 1 // +0(コツ 1) + a = a + n // +0(コツ 1) + // +n(コツ 2) for i := 0; i < 5 * n + 1; i++ { fmt.Println(0) } - // +n*n (技法3) + // +n*n(コツ 3) for i := 0; i < 2 * n; i++ { for j := 0; j < n + 1; j++ { fmt.Println(0) @@ -732,13 +814,13 @@ $f(n)$が決まれば、時間計算量$O(f(n))$が得られます。しかし ```swift title="" func algorithm(n: Int) { - var a = 1 // +0 (技法1) - a = a + n // +0 (技法1) - // +n (技法2) + var a = 1 // +0(コツ 1) + a = a + n // +0(コツ 1) + // +n(コツ 2) for _ in 0 ..< (5 * n + 1) { print(0) } - // +n*n (技法3) + // +n*n(コツ 3) for _ in 0 ..< (2 * n) { for _ in 0 ..< (n + 1) { print(0) @@ -751,13 +833,13 @@ $f(n)$が決まれば、時間計算量$O(f(n))$が得られます。しかし ```javascript title="" function algorithm(n) { - let a = 1; // +0 (技法1) - a = a + n; // +0 (技法1) - // +n (技法2) + let a = 1; // +0(コツ 1) + a = a + n; // +0(コツ 1) + // +n(コツ 2) for (let i = 0; i < 5 * n + 1; i++) { console.log(0); } - // +n*n (技法3) + // +n*n(コツ 3) for (let i = 0; i < 2 * n; i++) { for (let j = 0; j < n + 1; j++) { console.log(0); @@ -770,13 +852,13 @@ $f(n)$が決まれば、時間計算量$O(f(n))$が得られます。しかし ```typescript title="" function algorithm(n: number): void { - let a = 1; // +0 (技法1) - a = a + n; // +0 (技法1) - // +n (技法2) + let a = 1; // +0(コツ 1) + a = a + n; // +0(コツ 1) + // +n(コツ 2) for (let i = 0; i < 5 * n + 1; i++) { console.log(0); } - // +n*n (技法3) + // +n*n(コツ 3) for (let i = 0; i < 2 * n; i++) { for (let j = 0; j < n + 1; j++) { console.log(0); @@ -789,13 +871,13 @@ $f(n)$が決まれば、時間計算量$O(f(n))$が得られます。しかし ```dart title="" void algorithm(int n) { - int a = 1; // +0 (技法1) - a = a + n; // +0 (技法1) - // +n (技法2) + int a = 1; // +0(コツ 1) + a = a + n; // +0(コツ 1) + // +n(コツ 2) for (int i = 0; i < 5 * n + 1; i++) { print(0); } - // +n*n (技法3) + // +n*n(コツ 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { print(0); @@ -808,15 +890,15 @@ $f(n)$が決まれば、時間計算量$O(f(n))$が得られます。しかし ```rust title="" fn algorithm(n: i32) { - let mut a = 1; // +0 (技法1) - a = a + n; // +0 (技法1) + let mut a = 1; // +0(コツ 1) + a = a + n; // +0(コツ 1) - // +n (技法2) + // +n(コツ 2) for i in 0..(5 * n + 1) { println!("{}", 0); } - // +n*n (技法3) + // +n*n(コツ 3) for i in 0..(2 * n) { for j in 0..(n + 1) { println!("{}", 0); @@ -829,13 +911,13 @@ $f(n)$が決まれば、時間計算量$O(f(n))$が得られます。しかし ```c title="" void algorithm(int n) { - int a = 1; // +0 (技法1) - a = a + n; // +0 (技法1) - // +n (技法2) + int a = 1; // +0(コツ 1) + a = a + n; // +0(コツ 1) + // +n(コツ 2) for (int i = 0; i < 5 * n + 1; i++) { printf("%d", 0); } - // +n*n (技法3) + // +n*n(コツ 3) for (int i = 0; i < 2 * n; i++) { for (int j = 0; j < n + 1; j++) { printf("%d", 0); @@ -847,193 +929,223 @@ $f(n)$が決まれば、時間計算量$O(f(n))$が得られます。しかし === "Kotlin" ```kotlin title="" - + fun algorithm(n: Int) { + var a = 1 // +0(コツ 1) + a = a + n // +0(コツ 1) + // +n(コツ 2) + for (i in 0..<5 * n + 1) { + println(0) + } + // +n*n(コツ 3) + for (i in 0..<2 * n) { + for (j in 0.. 表: 異なる操作カウントに対する時間計算量

+

  異なる操作回数に対応する時間計算量

-| 操作カウント $T(n)$ | 時間計算量 $O(f(n))$ | -| ---------------------- | ------------------------- | -| $100000$ | $O(1)$ | -| $3n + 2$ | $O(n)$ | -| $2n^2 + 3n + 2$ | $O(n^2)$ | -| $n^3 + 10000n^2$ | $O(n^3)$ | -| $2^n + 10000n^{10000}$ | $O(2^n)$ | +| 操作回数 $T(n)$ | 時間計算量 $O(f(n))$ | +| ---------------------- | -------------------- | +| $100000$ | $O(1)$ | +| $3n + 2$ | $O(n)$ | +| $2n^2 + 3n + 2$ | $O(n^2)$ | +| $n^3 + 10000n^2$ | $O(n^3)$ | +| $2^n + 10000n^{10000}$ | $O(2^n)$ | -## 一般的な時間計算量の種類 +## よくある種類 -入力データサイズを$n$としましょう。一般的な時間計算量の種類を下図に示し、低いものから高いものへと並べています: +入力データサイズを $n$ とすると、よくある時間計算量の種類は次図のとおりです(小さい順に並べています)。 $$ \begin{aligned} -& O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline -& \text{定数} < \text{対数} < \text{線形} < \text{線形対数} < \text{二次} < \text{指数} < \text{階乗} +O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline +\text{定数階} < \text{対数階} < \text{線形階} < \text{線形対数階} < \text{平方階} < \text{指数階} < \text{階乗階} \end{aligned} $$ -![Common types of time complexity](time_complexity.assets/time_complexity_common_types.png) +![よくある時間計算量の種類](time_complexity.assets/time_complexity_common_types.png) -### 定数オーダー $O(1)$ +### 定数階 $O(1)$ -定数オーダーは、操作数が入力データサイズ$n$とは無関係であることを意味します。以下の関数では、操作数`size`が大きい場合でも、$n$とは無関係であるため、時間計算量は$O(1)$のままです: +定数階の操作回数は入力データサイズ $n$ と無関係であり、$n$ が変化しても増減しません。 + +次の関数では、操作回数 `size` が大きい可能性はありますが、入力データサイズ $n$ とは無関係なので、時間計算量は依然として $O(1)$ です: ```src [file]{time_complexity}-[class]{}-[func]{constant} ``` -### 線形オーダー $O(n)$ +### 線形階 $O(n)$ -線形オーダーは、操作数が入力データサイズ$n$と線形に増加することを示します。線形オーダーは一般的に単一ループ構造で現れます: +線形階の操作回数は入力データサイズ $n$ に対して線形に増加します。線形階は通常、単一ループに現れます: ```src [file]{time_complexity}-[class]{}-[func]{linear} ``` -配列の走査や連結リストの走査などの操作は時間計算量が$O(n)$で、$n$は配列またはリストの長さです: +配列走査や連結リスト走査などの操作の時間計算量はいずれも $O(n)$ であり、ここでの $n$ は配列または連結リストの長さです: ```src [file]{time_complexity}-[class]{}-[func]{array_traversal} ``` -**入力データサイズ$n$は入力データの種類に基づいて決定する必要があります**。例えば、最初の例では、$n$は入力データサイズを表し、2番目の例では、配列の長さ$n$がデータサイズです。 +注意すべきなのは、**入力データサイズ $n$ は入力データの型に応じて具体的に定める必要がある**ということです。例えば 1 つ目の例では変数 $n$ が入力データサイズであり、2 つ目の例では配列長 $n$ がデータサイズです。 -### 二次オーダー $O(n^2)$ +### 平方階 $O(n^2)$ -二次オーダーは、操作数が入力データサイズ$n$の二乗に比例して増加することを意味します。二次オーダーは通常ネストしたループで現れ、外側と内側のループの両方が時間計算量$O(n)$を持ち、全体の複雑度は$O(n^2)$になります: +平方階の操作回数は入力データサイズ $n$ に対して二乗のオーダーで増加します。平方階は通常、入れ子ループに現れ、外側のループと内側のループの時間計算量がともに $O(n)$ であるため、全体の時間計算量は $O(n^2)$ になります: ```src [file]{time_complexity}-[class]{}-[func]{quadratic} ``` -下図は定数オーダー、線形オーダー、二次オーダーの時間計算量を比較しています。 +以下の図は、定数階・線形階・平方階の 3 種類の時間計算量を比較したものです。 -![Constant, linear, and quadratic order time complexities](time_complexity.assets/time_complexity_constant_linear_quadratic.png) +![定数階、線形階、平方階の時間計算量](time_complexity.assets/time_complexity_constant_linear_quadratic.png) -例えば、バブルソートでは、外側のループが$n - 1$回実行され、内側のループが$n-1$、$n-2$、...、$2$、$1$回実行され、平均$n / 2$回となり、時間計算量は$O((n - 1) n / 2) = O(n^2)$になります: +バブルソートを例にとると、外側のループは $n - 1$ 回実行され、内側のループは $n-1$、$n-2$、$\dots$、$2$、$1$ 回実行され、平均すると $n / 2$ 回です。したがって時間計算量は $O((n - 1) n / 2) = O(n^2)$ となります: ```src [file]{time_complexity}-[class]{}-[func]{bubble_sort} ``` -### 指数オーダー $O(2^n)$ +### 指数階 $O(2^n)$ -生物学的「細胞分裂」は指数オーダー増加の典型例です:1つの細胞から始まり、1回の分裂後に2つ、2回の分裂後に4つとなり、$n$回の分裂後に$2^n$個の細胞になります。 +生物学における「細胞分裂」は指数階増加の典型例です。初期状態では細胞が $1$ 個あり、1 回分裂すると $2$ 個、2 回分裂すると $4$ 個となり、以下同様に、$n$ 回分裂すると $2^n$ 個の細胞になります。 -下図とコードは細胞分裂プロセスをシミュレートし、時間計算量は$O(2^n)$です: +以下の図とコードは細胞分裂の過程を模擬したもので、時間計算量は $O(2^n)$ です。なお、入力の $n$ は分裂回数を表し、戻り値 `count` は総分裂回数を表します。 ```src [file]{time_complexity}-[class]{}-[func]{exponential} ``` -![Exponential order time complexity](time_complexity.assets/time_complexity_exponential.png) +![指数階の時間計算量](time_complexity.assets/time_complexity_exponential.png) -実際には、指数オーダーは再帰関数でよく現れます。例えば、以下のコードでは、再帰的に2つの半分に分割し、$n$回の分割後に停止します: +実際のアルゴリズムでも、指数階は再帰関数によく現れます。例えば次のコードでは、再帰的に 2 つへ分岐し、$n$ 回分裂した後に停止します: ```src [file]{time_complexity}-[class]{}-[func]{exp_recur} ``` -指数オーダーの増加は極めて急速で、全数探索法(ブルートフォース、バックトラッキングなど)でよく見られます。大規模問題では、指数オーダーは受け入れられず、しばしば動的プログラミングや貪欲アルゴリズムが解決策として必要になります。 +指数階の増加は非常に速く、全探索法(ブルートフォース、バックトラッキングなど)によく見られます。データ規模が大きい問題では、指数階は受け入れられず、通常は動的計画法や貪欲法などを使って解く必要があります。 -### 対数オーダー $O(\log n)$ +### 対数階 $O(\log n)$ -指数オーダーとは対照的に、対数オーダーは「各ラウンドでサイズが半分になる」状況を反映します。入力データサイズが$n$の場合、各ラウンドでサイズが半分になるため、反復回数は$\log_2 n$で、これは$2^n$の逆関数です。 +指数階とは逆に、対数階は「各ラウンドで半分になる」状況を表します。入力データサイズを $n$ とすると、各ラウンドで半減するため、ループ回数は $\log_2 n$、すなわち $2^n$ の逆関数になります。 -下図とコードは「各ラウンドで半分にする」プロセスをシミュレートし、時間計算量は$O(\log_2 n)$で、一般的に$O(\log n)$と省略されます: +以下の図とコードは、「各ラウンドで半分になる」過程を模擬したもので、時間計算量は $O(\log_2 n)$、簡潔には $O(\log n)$ と書きます: ```src [file]{time_complexity}-[class]{}-[func]{logarithmic} ``` -![Logarithmic order time complexity](time_complexity.assets/time_complexity_logarithmic.png) +![対数階の時間計算量](time_complexity.assets/time_complexity_logarithmic.png) -指数オーダーと同様に、対数オーダーも再帰関数で頻繁に現れます。以下のコードは高さ$\log_2 n$の再帰木を形成します: +指数階と同様に、対数階も再帰関数によく現れます。次のコードは高さ $\log_2 n$ の再帰木を形成します: ```src [file]{time_complexity}-[class]{}-[func]{log_recur} ``` -対数オーダーは分割統治戦略に基づくアルゴリズムの典型で、「多くに分割」と「複雑な問題を単純化」するアプローチを体現しています。増加が遅く、定数オーダーの次に最も理想的な時間計算量です。 +対数階は分割統治に基づくアルゴリズムによく現れ、「1 つを複数に分ける」「複雑なものを単純化する」という考え方を体現しています。増加は緩やかで、定数階に次いで理想的な時間計算量です。 -!!! tip "$O(\log n)$の底は何ですか?" +!!! tip "$O(\log n)$ の底は何か?" - 技術的には、「$m$に分割」は時間計算量$O(\log_m n)$に対応します。対数の底変更公式を使用すると、異なる対数複雑度を等価にできます: + 正確には、「$m$ 個に分ける」場合に対応する時間計算量は $O(\log_m n)$ です。そして対数の底の変換公式により、底が異なっても同値な時間計算量が得られます: $$ O(\log_m n) = O(\log_k n / \log_k m) = O(\log_k n) $$ - これは、底$m$を複雑度に影響を与えることなく変更できることを意味します。したがって、しばしば底$m$を省略し、単に対数オーダーを$O(\log n)$と表記します。 + つまり、底 $m$ は複雑度に影響を与えずに変換できます。そのため通常は底 $m$ を省略し、対数階を単に $O(\log n)$ と記します。 -### 線形対数オーダー $O(n \log n)$ +### 線形対数階 $O(n \log n)$ -線形対数オーダーはネストしたループでよく現れ、2つのループの複雑度がそれぞれ$O(\log n)$と$O(n)$です。関連するコードは以下の通りです: +線形対数階は入れ子ループによく現れ、2 層のループの時間計算量はそれぞれ $O(\log n)$ と $O(n)$ です。関連するコードは次のとおりです: ```src [file]{time_complexity}-[class]{}-[func]{linear_log_recur} ``` -下図は線形対数オーダーがどのように生成されるかを示しています。二分木の各レベルには$n$個の操作があり、木には$\log_2 n + 1$レベルがあり、時間計算量は$O(n \log n)$になります。 +下図は線形対数階がどのように生じるかを示しています。二分木の各層の操作総数はすべて $n$ であり、木全体は $\log_2 n + 1$ 層あるため、時間計算量は $O(n \log n)$ です。 -![Linear-logarithmic order time complexity](time_complexity.assets/time_complexity_logarithmic_linear.png) +![線形対数階の時間計算量](time_complexity.assets/time_complexity_logarithmic_linear.png) -主流のソートアルゴリズムは通常$O(n \log n)$の時間計算量を持ち、クイックソート、マージソート、ヒープソートなどがあります。 +主なソートアルゴリズムの時間計算量は通常 $O(n \log n)$ であり、例えばクイックソート、マージソート、ヒープソートなどがあります。 -### 階乗オーダー $O(n!)$ +### 階乗階 $O(n!)$ -階乗オーダーは「全順列」の数学問題に対応します。$n$個の異なる要素が与えられた場合、可能な順列の総数は: +階乗階は、数学における「全順列」の問題に対応します。互いに重複しない $n$ 個の要素が与えられたとき、そのすべての並べ方を求めると、通り数は次のようになります: $$ n! = n \times (n - 1) \times (n - 2) \times \dots \times 2 \times 1 $$ -階乗は通常再帰を使用して実装されます。以下のコードと図に示されているように、第1レベルは$n$個の分岐に分割され、第2レベルは$n - 1$個の分岐に分割され、第$n$レベル後に停止します: +階乗は通常、再帰で実装されます。以下の図とコードのように、第 1 層では $n$ 個に分岐し、第 2 層では $n - 1$ 個に分岐し、以下同様に、第 $n$ 層で分岐が停止します: ```src [file]{time_complexity}-[class]{}-[func]{factorial_recur} ``` -![Factorial order time complexity](time_complexity.assets/time_complexity_factorial.png) +![階乗階の時間計算量](time_complexity.assets/time_complexity_factorial.png) -階乗オーダーは指数オーダーよりもさらに速く増加することに注意してください。より大きな$n$値では受け入れられません。 +注意すべき点として、$n \geq 4$ なら常に $n! > 2^n$ なので、階乗階は指数階よりもさらに速く増加し、$n$ が大きい場合にはやはり受け入れられません。 -## 最悪、最良、平均時間計算量 +## 最悪・最良・平均時間計算量 -**アルゴリズムの時間効率は固定されていないことが多く、入力データの分布に依存します**。長さ$n$の配列`nums`があり、$1$から$n$までの数で構成され、それぞれが一度だけ現れますが、ランダムにシャッフルされた順序であるとします。タスクは要素$1$のインデックスを返すことです。以下の結論を導けます: +**アルゴリズムの時間効率は固定ではなく、入力データの分布に左右されることが多いです**。長さ $n$ の配列 `nums` を考えます。`nums` は $1$ から $n$ までの数字で構成され、各数字は 1 回だけ現れます。ただし要素の順序はランダムにシャッフルされており、目標は要素 $1$ のインデックスを返すことです。ここから次の結論が得られます。 -- `nums = [?, ?, ..., 1]`の場合、つまり最後の要素が$1$の場合、配列の完全な走査が必要で、**最悪ケース時間計算量$O(n)$を達成します**。 -- `nums = [1, ?, ?, ...]`の場合、つまり最初の要素が$1$の場合、配列の長さに関係なく、さらなる走査は不要で、**最良ケース時間計算量$\Omega(1)$を達成します**。 +- `nums = [?, ?, ..., 1]`、つまり末尾の要素が $1$ の場合は、配列全体を最後まで走査する必要があり、**最悪時間計算量 $O(n)$** になります。 +- `nums = [1, ?, ?, ...]`、つまり先頭要素が $1$ の場合は、配列がどれだけ長くてもそれ以上走査する必要がなく、**最良時間計算量 $\Omega(1)$** になります。 -「最悪ケース時間計算量」は漸近上限に対応し、大きな$O$記法で表されます。対応して、「最良ケース時間計算量」は漸近下限に対応し、$\Omega$で表されます: +「最悪時間計算量」は関数の漸近上界に対応し、ビッグ $O$ 記法で表します。同様に、「最良時間計算量」は関数の漸近下界に対応し、$\Omega$ 記法で表します: ```src [file]{worst_best_time_complexity}-[class]{}-[func]{find_one} ``` -最良ケース時間計算量は実際にはほとんど使用されないことに注意してください。通常は非常に低い確率でのみ達成可能で、誤解を招く可能性があるからです。**最悪ケース時間計算量はより実用的で、効率の安全値を提供し**、アルゴリズムを自信を持って使用できるようにします。 +実際には、最良時間計算量を使うことはあまりありません。通常それが実現する確率はごく低く、誤解を招く可能性があるからです。**一方で最悪時間計算量はより実用的で、効率の安全側の目安を与えてくれる**ため、安心してアルゴリズムを使えます。 -上記の例から、最悪ケースと最良ケースの時間計算量は両方とも「特殊なデータ分布」下でのみ発生し、発生確率が小さく、アルゴリズムの実行効率を正確に反映しない可能性があることが明らかです。対照的に、**平均時間計算量はランダム入力データ下でのアルゴリズムの効率を反映でき**、$\Theta$記法で表されます。 +上の例から分かるように、最悪時間計算量と最良時間計算量は「特殊なデータ分布」でのみ現れ、その発生確率は低いことが多く、アルゴリズムの実行効率をそのまま正確に反映するわけではありません。それに対して、**平均時間計算量はランダム入力におけるアルゴリズムの実行効率を表せる**ため、$\Theta$ 記法で表します。 -一部のアルゴリズムでは、ランダムデータ分布下での平均ケースを簡単に推定できます。例えば、前述の例では、入力配列がシャッフルされているため、要素$1$が任意のインデックスに現れる確率は等しいです。したがって、アルゴリズムの平均ループ数は配列長さの半分$n / 2$で、平均時間計算量は$\Theta(n / 2) = \Theta(n)$です。 +一部のアルゴリズムでは、ランダムなデータ分布における平均的な状況を比較的簡単に求められます。例えば上の例では、入力配列はシャッフルされているため、要素 $1$ が任意のインデックスに現れる確率は等しいです。したがってアルゴリズムの平均ループ回数は配列長の半分 $n / 2$ となり、平均時間計算量は $\Theta(n / 2) = \Theta(n)$ です。 -しかし、より複雑なアルゴリズムの平均時間計算量を計算することは非常に困難です。データ分布下での全体的な数学的期待値を分析することが困難だからです。そのような場合、通常はアルゴリズムの効率を判断する基準として最悪ケース時間計算量を使用します。 +しかし、より複雑なアルゴリズムでは、平均時間計算量を計算するのはしばしば困難です。データ分布に対する全体の数学的期待値を分析するのが難しいからです。そのような場合、通常は最悪時間計算量をアルゴリズム効率の評価基準として用います。 -!!! question "$\Theta$記号はなぜほとんど見られないのですか?" +!!! question "なぜ $\Theta$ 記号をあまり見かけないのか?" - おそらく$O$記法がより一般的に話されるため、平均時間計算量を表すためによく使用されます。しかし、厳密に言えば、この実践は正確ではありません。この本や他の資料で「平均時間計算量$O(n)$」のような表現に遭遇した場合は、直接$\Theta(n)$として理解してください。 + おそらく $O$ 記号のほうが口にしやすいため、平均時間計算量を表すのにもよく使われます。ただし厳密には、この用法は正確ではありません。本書や他の資料で「平均時間計算量 $O(n)$」のような表現を見かけた場合は、そのまま $\Theta(n)$ と理解してください。 diff --git a/ja/docs/chapter_data_structure/basic_data_types.md b/ja/docs/chapter_data_structure/basic_data_types.md index f91e34e3d..afd06141d 100644 --- a/ja/docs/chapter_data_structure/basic_data_types.md +++ b/ja/docs/chapter_data_structure/basic_data_types.md @@ -1,66 +1,66 @@ # 基本データ型 -コンピュータ内のデータについて考える際、テキスト、画像、動画、音声、3Dモデルなど、様々な形式が思い浮かびます。これらの組織的な形式は異なりますが、すべて様々な基本データ型から構成されています。 +コンピュータ内のデータについて考えるとき、テキスト、画像、動画、音声、3D モデルなど、さまざまな形態を思い浮かべます。これらのデータの構成形式はそれぞれ異なりますが、いずれも各種の基本データ型によって成り立っています。 -**基本データ型とは、CPUが直接操作できるもの**であり、アルゴリズムで直接使用されます。主に以下が含まれます。 +**基本データ型は CPU が直接演算できる型**であり、アルゴリズムの中で直接使われます。主なものは次のとおりです。 -- 整数型:`byte`、`short`、`int`、`long` -- 浮動小数点型:`float`、`double`、小数を表現するために使用 -- 文字型:`char`、様々な言語の文字、句読点、さらには絵文字を表現するために使用 -- ブール型:`bool`、「はい」または「いいえ」の判断を表現するために使用 +- 整数型 `byte`、`short`、`int`、`long` 。 +- 浮動小数点数型 `float`、`double` ,小数を表すために使います。 +- 文字型 `char` ,各言語の文字、句読点、さらには絵文字などを表すために使います。 +- 真偽値型 `bool` ,真か偽かの判定を表すために使います。 -**基本データ型は、コンピュータ内で二進形式で格納されます**。1つの二進桁は1ビットです。ほとんどの現代的なオペレーティングシステムでは、1バイトは8ビットで構成されています。 +**基本データ型はコンピュータ内で 2 進数の形で格納されます**。1 つの二進桁は $1$ ビットです。現代のほとんどのオペレーティングシステムでは、$1$ バイト(byte)は $8$ ビット(bit)で構成されます。 -基本データ型の値の範囲は、それらが占める空間のサイズに依存します。以下では、Javaを例に説明します。 +基本データ型の値域は、その型が占める領域の大きさによって決まります。以下では Java を例に取ります。 -- 整数型`byte`は1バイト = 8ビットを占め、$2^8$個の数値を表現できます。 -- 整数型`int`は4バイト = 32ビットを占め、$2^{32}$個の数値を表現できます。 +- 整数型 `byte` は $1$ バイト = $8$ ビットを占め、$2^{8}$ 個の数を表せます。 +- 整数型 `int` は $4$ バイト = $32$ ビットを占め、$2^{32}$ 個の数を表せます。 -以下の表は、Javaにおける様々な基本データ型が占める空間、値の範囲、デフォルト値を示しています。この表を暗記する必要はありませんが、一般的な理解を持ち、必要時に参照することをお勧めします。 +下表は、Java における各種基本データ型の使用領域、値域、デフォルト値を示したものです。この表を丸暗記する必要はなく、大まかに理解しておけば十分であり、必要になったときに参照すればかまいません。 -

  基本データ型が占める空間と値の範囲

+

  基本データ型の使用領域と値域

-| 型 | シンボル | 占有空間 | 最小値 | 最大値 | デフォルト値 | -| ------- | -------- | -------- | ------------------------ | ----------------------- | -------------- | -| 整数 | `byte` | 1バイト | $-2^7$ ($-128$) | $2^7 - 1$ ($127$) | 0 | -| | `short` | 2バイト | $-2^{15}$ | $2^{15} - 1$ | 0 | -| | `int` | 4バイト | $-2^{31}$ | $2^{31} - 1$ | 0 | -| | `long` | 8バイト | $-2^{63}$ | $2^{63} - 1$ | 0 | -| 浮動小数点 | `float` | 4バイト | $1.175 \times 10^{-38}$ | $3.403 \times 10^{38}$ | $0.0\text{f}$ | -| | `double` | 8バイト | $2.225 \times 10^{-308}$ | $1.798 \times 10^{308}$ | 0.0 | -| 文字 | `char` | 2バイト | 0 | $2^{16} - 1$ | 0 | -| ブール | `bool` | 1バイト | $\text{false}$ | $\text{true}$ | $\text{false}$ | +| 型 | 記号 | 使用領域 | 最小値 | 最大値 | デフォルト値 | +| ------ | -------- | -------- | ------------------------ | ----------------------- | -------------- | +| 整数 | `byte` | 1 バイト | $-2^7$ ($-128$) | $2^7 - 1$ ($127$) | $0$ | +| | `short` | 2 バイト | $-2^{15}$ | $2^{15} - 1$ | $0$ | +| | `int` | 4 バイト | $-2^{31}$ | $2^{31} - 1$ | $0$ | +| | `long` | 8 バイト | $-2^{63}$ | $2^{63} - 1$ | $0$ | +| 浮動小数点数 | `float` | 4 バイト | $1.175 \times 10^{-38}$ | $3.403 \times 10^{38}$ | $0.0\text{f}$ | +| | `double` | 8 バイト | $2.225 \times 10^{-308}$ | $1.798 \times 10^{308}$ | $0.0$ | +| 文字 | `char` | 2 バイト | $0$ | $2^{16} - 1$ | $0$ | +| 真偽値 | `bool` | 1 バイト | $\text{false}$ | $\text{true}$ | $\text{false}$ | -上記の表はJavaの基本データ型に特有であることにご注意ください。すべてのプログラミング言語には独自のデータ型定義があり、占有空間、値の範囲、デフォルト値が異なる場合があります。 +注意してください。上表は Java の基本データ型に対するものです。各プログラミング言語にはそれぞれ独自のデータ型定義があり、使用領域、値域、デフォルト値は異なる場合があります。 -- Pythonでは、整数型`int`は任意のサイズになることができ、利用可能なメモリによってのみ制限されます。浮動小数点`float`は倍精度64ビットです。`char`型は存在せず、単一文字は実際には長さ1の文字列`str`です。 -- CおよびC++では基本データ型のサイズが指定されておらず、実装とプラットフォームによって異なります。上記の表はLP64[データモデル](https://en.cppreference.com/w/cpp/language/types#Properties)に従っており、LinuxやmacOSを含むUnix 64ビットオペレーティングシステムで使用されています。 -- CおよびC++における`char`のサイズは1バイトですが、ほとんどのプログラミング言語では、特定の文字エンコーディング方法に依存し、詳細は「文字エンコーディング」の章で説明されています。 -- ブール値の表現には1ビット(0または1)のみが必要ですが、通常はメモリ内に1バイトとして格納されます。これは、現代のコンピュータCPUが通常1バイトを最小のアドレス可能なメモリ単位として使用するためです。 +- Python では、整数型 `int` は利用可能なメモリに制限されるだけで任意の大きさを取れます。浮動小数点数 `float` は倍精度 64 ビットです。`char` 型はなく、1 文字は実際には長さ 1 の文字列 `str` です。 +- C と C++ では基本データ型の大きさは明確に規定されておらず、実装やプラットフォームによって異なります。上表は LP64 [データモデル](https://en.cppreference.com/w/cpp/language/types#Properties) に従っており、Linux や macOS を含む Unix 系 64 ビット OS で用いられています。 +- `char` の大きさは C と C++ では 1 バイトですが、多くのプログラミング言語では採用する文字エンコーディング方式によって決まります。詳しくは「文字エンコーディング」の章を参照してください。 +- 真偽値を表すのに必要なのは 1 ビット($0$ または $1$)だけですが、メモリ上では通常 1 バイトとして格納されます。これは、現代のコンピュータ CPU が通常 1 バイトを最小のアドレス指定可能なメモリ単位としているためです。 -では、基本データ型とデータ構造の関係は何でしょうか?データ構造とは、コンピュータ内でデータを組織化し格納する方法であることを知っています。ここでの焦点は「データ」ではなく「構造」です。 +では、基本データ型とデータ構造の間にはどのような関係があるのでしょうか。データ構造とは、コンピュータ内でデータを組織し格納する方法のことです。この言葉で主役なのは「データ」ではなく「構造」です。 -「数値の列」を表現したい場合、自然に配列の使用を考えます。これは、配列の線形構造が数値の隣接性と順序性を表現できるためですが、格納される内容が整数`int`、小数`float`、文字`char`のいずれであっても、「データ構造」とは無関係です。 +「数字の並び」を表したいなら、自然に配列の使用を思い浮かべるでしょう。これは、配列の線形構造が数字どうしの隣接関係や順序関係を表せるからです。しかし、格納する内容が整数 `int` なのか、小数 `float` なのか、文字 `char` なのかは、「データ構造」とは関係ありません。 -言い換えると、**基本データ型はデータの「内容型」を提供し、データ構造はデータの「組織化方法」を提供します**。例えば、以下のコードでは、同じデータ構造(配列)を使用して、`int`、`float`、`char`、`bool`などの異なる基本データ型を格納し表現しています。 +言い換えると、**基本データ型はデータの「内容の型」を提供し、データ構造はデータの「組織方法」を提供します**。たとえば次のコードでは、同じデータ構造(配列)を使って `int`、`float`、`char`、`bool` など異なる基本データ型を格納・表現しています。 === "Python" ```python title="" - # 様々な基本データ型を使用して配列を初期化 + # さまざまな基本データ型で配列を初期化する numbers: list[int] = [0] * 5 decimals: list[float] = [0.0] * 5 - # Pythonの文字は実際には長さ1の文字列 + # Python の文字は実際には長さ 1 の文字列 characters: list[str] = ['0'] * 5 bools: list[bool] = [False] * 5 - # Pythonのリストは様々な基本データ型とオブジェクト参照を自由に格納可能 + # Python のリストはさまざまな基本データ型とオブジェクト参照を自由に格納できる data = [0, 0.0, 'a', False, ListNode(0)] ``` === "C++" ```cpp title="" - // 様々な基本データ型を使用して配列を初期化 + // さまざまな基本データ型で配列を初期化する int numbers[5]; float decimals[5]; char characters[5]; @@ -70,7 +70,7 @@ === "Java" ```java title="" - // 様々な基本データ型を使用して配列を初期化 + // さまざまな基本データ型で配列を初期化する int[] numbers = new int[5]; float[] decimals = new float[5]; char[] characters = new char[5]; @@ -80,7 +80,7 @@ === "C#" ```csharp title="" - // 様々な基本データ型を使用して配列を初期化 + // さまざまな基本データ型で配列を初期化する int[] numbers = new int[5]; float[] decimals = new float[5]; char[] characters = new char[5]; @@ -90,7 +90,7 @@ === "Go" ```go title="" - // 様々な基本データ型を使用して配列を初期化 + // さまざまな基本データ型で配列を初期化する var numbers = [5]int{} var decimals = [5]float64{} var characters = [5]byte{} @@ -100,7 +100,7 @@ === "Swift" ```swift title="" - // 様々な基本データ型を使用して配列を初期化 + // さまざまな基本データ型で配列を初期化する let numbers = Array(repeating: 0, count: 5) let decimals = Array(repeating: 0.0, count: 5) let characters: [Character] = Array(repeating: "a", count: 5) @@ -110,14 +110,14 @@ === "JS" ```javascript title="" - // JavaScriptの配列は様々な基本データ型とオブジェクトを自由に格納可能 + // JavaScript の配列はさまざまな基本データ型とオブジェクトを自由に格納できる const array = [0, 0.0, 'a', false]; ``` === "TS" ```typescript title="" - // 様々な基本データ型を使用して配列を初期化 + // さまざまな基本データ型で配列を初期化する const numbers: number[] = []; const characters: string[] = []; const bools: boolean[] = []; @@ -126,7 +126,7 @@ === "Dart" ```dart title="" - // 様々な基本データ型を使用して配列を初期化 + // さまざまな基本データ型で配列を初期化する List numbers = List.filled(5, 0); List decimals = List.filled(5, 0.0); List characters = List.filled(5, 'a'); @@ -136,9 +136,9 @@ === "Rust" ```rust title="" - // 様々な基本データ型を使用して配列を初期化 + // さまざまな基本データ型で配列を初期化する let numbers: Vec = vec![0; 5]; - let decimals: Vec = vec![0.0, 5]; + let decimals: Vec = vec![0.0; 5]; let characters: Vec = vec!['0'; 5]; let bools: Vec = vec![false; 5]; ``` @@ -146,7 +146,7 @@ === "C" ```c title="" - // 様々な基本データ型を使用して配列を初期化 + // さまざまな基本データ型で配列を初期化する int numbers[10]; float decimals[10]; char characters[10]; @@ -156,6 +156,20 @@ === "Kotlin" ```kotlin title="" - + // さまざまな基本データ型で配列を初期化する + val numbers = IntArray(5) + val decinals = FloatArray(5) + val characters = CharArray(5) + val bools = BooleanArray(5) ``` +=== "Ruby" + + ```ruby title="" + # Ruby のリストはさまざまな基本データ型とオブジェクト参照を自由に格納できる + data = [0, 0.0, 'a', false, ListNode(0)] + ``` + +??? pythontutor "実行の可視化" + + https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E5%A4%9A%E7%A7%8D%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E6%9D%A5%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20numbers%20%3D%20%5B0%5D%20*%205%0A%20%20%20%20decimals%20%3D%20%5B0.0%5D%20*%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%AD%97%E7%AC%A6%E5%AE%9E%E9%99%85%E4%B8%8A%E6%98%AF%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%0A%20%20%20%20characters%20%3D%20%5B'0'%5D%20*%205%0A%20%20%20%20bools%20%3D%20%5BFalse%5D%20*%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%88%97%E8%A1%A8%E5%8F%AF%E4%BB%A5%E8%87%AA%E7%94%B1%E5%AD%98%E5%82%A8%E5%90%84%E7%A7%8D%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%92%8C%E5%AF%B9%E8%B1%A1%E5%BC%95%E7%94%A8%0A%20%20%20%20data%20%3D%20%5B0,%200.0,%20'a',%20False,%20ListNode%280%29%5D&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false diff --git a/ja/docs/chapter_data_structure/character_encoding.md b/ja/docs/chapter_data_structure/character_encoding.md index e2a376ebd..8a4fdc75a 100644 --- a/ja/docs/chapter_data_structure/character_encoding.md +++ b/ja/docs/chapter_data_structure/character_encoding.md @@ -1,87 +1,87 @@ # 文字エンコーディング * -コンピュータシステムでは、すべてのデータが二進形式で格納され、`char`も例外ではありません。文字を表現するために、各文字と二進数の一対一のマッピングを定義する「文字セット」を開発する必要があります。文字セットがあれば、コンピュータは表を参照して二進数を文字に変換できます。 +コンピュータでは、すべてのデータは二進数の形で保存されており、文字 `char` も例外ではありません。文字を表すためには、「文字セット」を定義し、各文字と二進数の間の一対一の対応関係を定める必要があります。文字セットがあれば、コンピュータは対応表を参照して二進数から文字への変換を行えます。 -## ASCII文字セット +## ASCII 文字セット -ASCIIコードは最も初期の文字セットの一つで、正式にはAmerican Standard Code for Information Interchangeとして知られています。7つの二進桁(1バイトの下位7ビット)を使用して文字を表現し、最大128種類の異なる文字を表現できます。以下の図に示すように、ASCIIには英語の大文字と小文字、0〜9の数字、様々な句読点、特定の制御文字(改行やタブなど)が含まれています。 +ASCII コードは最も早く登場した文字セットで、その正式名称は American Standard Code for Information Interchange(米国標準情報交換コード)です。これは 7 ビットの二進数(1 バイトの下位 7 ビット)で 1 文字を表し、最大で 128 種類の異なる文字を表現できます。下図のように、ASCII コードには英字の大文字と小文字、数字 0 ~ 9、いくつかの句読点、そしていくつかの制御文字(改行やタブなど)が含まれます。 -![ASCIIコード](character_encoding.assets/ascii_table.png) +![ASCII コード](character_encoding.assets/ascii_table.png) -しかし、**ASCIIは英語の文字のみを表現できます**。コンピュータのグローバル化に伴い、より多くの言語を表現するためにEASCIIと呼ばれる文字セットが開発されました。ASCIIの7ビット構造から8ビットに拡張し、256文字の表現を可能にしました。 +しかし、**ASCII コードで表現できるのは英語だけです**。コンピュータのグローバル化に伴い、より多くの言語を表せる EASCII 文字セットが生まれました。これは ASCII の 7 ビットを 8 ビットへ拡張したもので、256 種類の異なる文字を表現できます。 -世界的に、様々な地域固有のEASCII文字セットが導入されました。これらのセットの最初の128文字はASCIIと一致していますが、残りの128文字は異なる言語の要件に対応するために異なって定義されています。 +世界では、さまざまな地域に適した EASCII 文字セットが次々に登場しました。これらの文字セットでは、前半の 128 文字は ASCII コードで統一され、後半の 128 文字は各言語の要件に合わせて個別に定義されています。 -## GBK文字セット +## GBK 文字セット -後に、**EASCIIでも多くの言語の文字要件を満たすことができない**ことが判明しました。例えば、中国語には約10万の漢字があり、そのうち数千が定期的に使用されています。1980年、中国標準化委員会は6763の中国語文字を含むGB2312文字セットを発表し、中国語のコンピュータ処理ニーズを本質的に満たしました。 +その後、人々は**EASCII コードでも多くの言語に必要な文字数を満たせない**ことに気づきました。たとえば漢字は 10 万字近くあり、日常的に使うものだけでも数千字あります。中国国家標準総局は 1980 年に GB2312 文字セットを公開し、6763 字の漢字を収録して、漢字のコンピュータ処理の基本的な需要を満たしました。 -しかし、GB2312は一部の稀少文字や繁体字を処理できませんでした。GBK文字セットはGB2312を拡張し、21886の中国語文字を含んでいます。GBKエンコーディングスキームでは、ASCII文字は1バイトで表現され、中国語文字は2バイトを使用します。 +しかし、GB2312 では一部の珍しい字や繁体字を扱えません。GBK 文字セットは GB2312 を基に拡張されたもので、合計 21886 字の漢字を収録しています。GBK のエンコーディング方式では、ASCII 文字は 1 バイト、漢字は 2 バイトで表されます。 -## Unicode文字セット +## Unicode 文字セット -コンピュータ技術の急速な発展と多数の文字セットおよびエンコーディング標準により、数多くの問題が発生しました。一方では、これらの文字セットは一般的に特定の言語の文字のみを定義し、多言語環境では適切に機能できませんでした。他方では、同じ言語に対する複数の文字セット標準の存在により、異なるエンコーディング標準を使用するコンピュータ間で情報交換を行う際に文字化けが発生しました。 +コンピュータ技術が急速に発展するにつれて、文字セットと符号化規格は百花繚乱の状態となり、それに伴って多くの問題も生じました。一方では、これらの文字セットは通常、特定の言語の文字しか定義しておらず、多言語環境では正常に動作できませんでした。もう一方では、同じ言語にも複数の文字セット規格が存在し、2 台のコンピュータが異なる符号化規格を使っていると、情報伝達の際に文字化けが発生しました。 -当時の研究者たちは考えました:**世界のすべての言語と記号を含む包括的な文字セットが開発されれば、言語横断環境と文字化けに関連する問題を解決できるのではないでしょうか?** このアイデアにインスパイアされて、広範囲な文字セットであるUnicodeが誕生しました。 +当時の研究者たちはこう考えました。**十分に完全な文字セットを打ち出して、世界中のあらゆる言語と記号をそこに収録すれば、多言語環境や文字化けの問題を解決できるのではないか**。この発想に後押しされて、大規模で包括的な文字セット Unicode が誕生しました。 -Unicodeは中国語で「统一码」(統一コード)と呼ばれ、理論的に100万文字以上を収容できます。世界中のすべての文字を単一のセットに組み込み、様々な言語の処理と表示のための汎用文字セットを提供し、異なるエンコーディング標準による文字化けの問題を減らすことを目指しています。 +Unicode の中国語名は「統一コード」であり、理論上は 100 万を超える文字を収容できます。Unicode は世界中の文字を 1 つの文字セットに統合することを目指し、さまざまな言語の文字を処理・表示できる汎用文字セットを提供することで、符号化規格の違いによる文字化けを減らそうとしています。 -1991年のリリース以来、Unicodeは新しい言語と文字を含むよう継続的に拡張されています。2022年9月現在、Unicodeには149,186文字が含まれており、様々な言語の文字、記号、さらには絵文字も含まれています。広大なUnicode文字セットでは、一般的に使用される文字は2バイトを占有し、一部の稀少な文字は3バイトまたは4バイトを占有する場合があります。 +1991 年の公開以来、Unicode は新しい言語と文字を継続的に拡充してきました。2022 年 9 月時点で、Unicode にはすでに 149186 文字が含まれており、各種言語の文字、記号、さらには絵文字まで収録されています。巨大な Unicode 文字セットでは、よく使われる文字は 2 バイトを占め、一部の珍しい文字は 3 バイト、さらには 4 バイトを占めます。 -Unicodeは各文字に数値(「コードポイント」と呼ばれる)を割り当てる汎用文字セットですが、**これらの文字コードポイントがコンピュータシステムにどのように格納されるべきかは指定していません**。疑問が生じるかもしれません:システムはテキスト内の異なる長さのUnicodeコードポイントをどのように解釈するのでしょうか?例えば、2バイトのコードが与えられた場合、システムはそれが単一の2バイト文字を表すのか、2つの1バイト文字を表すのかをどのように判断するのでしょうか? +Unicode は汎用文字セットであり、本質的には各文字に番号(「コードポイント」)を割り当てるものですが、**それらのコードポイントをコンピュータ内でどのように保存するかまでは規定していません**。ここで疑問が生じます。長さの異なる Unicode コードポイントが同じテキストに現れたとき、システムはどのように文字を解析するのでしょうか。たとえば長さ 2 バイトの符号が与えられたとき、それが 2 バイトの 1 文字なのか、1 バイトの 2 文字なのかをどう判定するのでしょうか。 -**この問題に対する簡単な解決策は、すべての文字を等長エンコーディングとして格納することです**。以下の図に示すように、「Hello」の各文字は1バイトを占有し、「算法」(アルゴリズム)の各文字は2バイトを占有します。上位ビットをゼロで埋めることで、「Hello 算法」のすべての文字を2バイトとしてエンコードできます。この方法により、システムは2バイトごとに文字を解釈し、フレーズの内容を復元できます。 +この問題に対して、**すべての文字を固定長の符号として保存する**という直接的な解決策があります。下図のように、「Hello」の各文字は 1 バイト、「アルゴリズム」の各文字は 2 バイトを占めます。上位ビットを 0 で埋めることで、「Hello アルゴリズム」のすべての文字を 2 バイト長にエンコードできます。こうすれば、システムは 2 バイトごとに 1 文字を解析して、この語句の内容を復元できます。 -![Unicodeエンコーディング例](character_encoding.assets/unicode_hello_algo.png) +![Unicode エンコーディングの例](character_encoding.assets/unicode_hello_algo.png) -しかし、ASCIIが示したように、英語のエンコーディングには1バイトのみが必要です。上記のアプローチを使用すると、英語テキストが占有する空間がASCIIエンコーディングと比較して2倍になり、メモリ空間の無駄になります。したがって、より効率的なUnicodeエンコーディング方法が必要です。 +しかし ASCII コードはすでに、英語の符号化には 1 バイトで十分であることを示しています。上記の方式を採用すると、英語のテキストが占める空間は ASCII エンコーディング時の 2 倍になり、メモリ空間の浪費が大きくなります。そのため、より効率的な Unicode エンコーディング方式が必要です。 -## UTF-8エンコーディング +## UTF-8 エンコーディング -現在、UTF-8は国際的に最も広く使用されているUnicodeエンコーディング方法になっています。**これは可変長エンコーディング**で、文字の複雑さに応じて1〜4バイトを使用して文字を表現します。ASCII文字は1バイトのみが必要で、ラテン文字とギリシャ文字は2バイト、一般的に使用される中国語文字は3バイト、その他の稀少な文字は4バイトが必要です。 +現在、UTF-8 は国際的に最も広く使われている Unicode エンコーディング方式になっています。**これは可変長のエンコーディング**であり、1 文字を 1 〜 4 バイトで表し、文字の複雑さに応じて長さが変わります。ASCII 文字は 1 バイト、ラテン文字とギリシャ文字は 2 バイト、一般的な漢字は 3 バイト、そのほかの一部の珍しい文字は 4 バイト必要です。 -UTF-8のエンコーディング規則は複雑ではなく、2つのケースに分けることができます: +UTF-8 の符号化規則はそれほど複雑ではなく、次の 2 つのケースに分けられます。 -- 1バイト文字の場合、最上位ビットを$0$に設定し、残りの7ビットをUnicodeコードポイントに設定します。注目すべきは、ASCII文字がUnicodeセットの最初の128コードポイントを占有することです。これは**UTF-8エンコーディングがASCIIと後方互換性がある**ことを意味します。これは、UTF-8を使用して古いASCIIテキストを解析できることを意味します。 -- 長さ$n$バイトの文字($n > 1$)の場合、最初のバイトの最上位$n$ビットを$1$に設定し、$(n + 1)^{\text{th}}$ビットを$0$に設定します。2番目のバイトから、各バイトの最上位2ビットを$10$に設定します。残りのビットはUnicodeコードポイントを埋めるために使用されます。 +- 長さ 1 バイトの文字では、最上位ビットを $0$ にし、残りの 7 ビットを Unicode コードポイントに設定します。ここで注意すべきなのは、ASCII 文字が Unicode 文字セットの先頭 128 個のコードポイントを占めていることです。つまり、**UTF-8 エンコーディングは ASCII コードと下位互換性があります**。このため、UTF-8 を使って古い ASCII コードのテキストを解析できます。 +- 長さ $n$ バイトの文字(ただし $n > 1$)では、先頭バイトの上位 $n$ ビットをすべて $1$ にし、第 $n + 1$ ビットを $0$ に設定します。2 バイト目以降では、各バイトの上位 2 ビットをいずれも $10$ にし、残りのすべてのビットで文字の Unicode コードポイントを埋めます。 -以下の図は「Hello算法」のUTF-8エンコーディングを示しています。最上位$n$ビットが$1$に設定されているため、システムは最上位ビットで$1$に設定されたビット数を数えることで文字の長さを$n$として決定できることが観察できます。 +下図は「Helloアルゴリズム」に対応する UTF-8 エンコーディングを示しています。観察すると、上位 $n$ ビットがすべて $1$ に設定されているため、システムは先頭から連続する $1$ の個数を読むことで、その文字の長さが $n$ であると解析できます。 -しかし、なぜ残りのバイトの最上位2ビットを$10$に設定するのでしょうか?実際、この$10$は一種のチェックサムとして機能します。システムが間違ったバイトからテキストの解析を開始した場合、バイトの先頭の$10$によりシステムは異常を迅速に検出できます。 +では、なぜ残りのすべてのバイトの上位 2 ビットを $10$ にするのでしょうか。実は、この $10$ は検査用の印として機能します。システムが誤ったバイト位置からテキストを解析し始めたとしても、バイト先頭の $10$ によって異常を素早く判定できます。 -$10$をチェックサムとして使用する理由は、UTF-8エンコーディング規則の下では、文字の最上位2ビットが$10$になることは不可能だからです。これは矛盾により証明できます:文字の最上位2ビットが$10$の場合、文字の長さが$1$であることを示し、これはASCIIに対応します。しかし、ASCII文字の最上位ビットは$0$であるべきで、これは仮定と矛盾します。 +この $10$ を検査用の印とする理由は、UTF-8 の符号化規則では上位 2 ビットが $10$ になる文字は存在しないからです。この結論は背理法で証明できます。ある文字の上位 2 ビットが $10$ だと仮定すると、その文字の長さは $1$ であり、ASCII コードに対応することになります。しかし ASCII コードの最上位ビットは $0$ であるはずなので、仮定と矛盾します。 -![UTF-8エンコーディング例](character_encoding.assets/utf-8_hello_algo.png) +![UTF-8 エンコーディングの例](character_encoding.assets/utf-8_hello_algo.png) -UTF-8以外にも、他の一般的なエンコーディング方法には以下があります: +UTF-8 以外にも、一般的なエンコーディング方式として次の 2 つがあります。 -- **UTF-16エンコーディング**:2または4バイトを使用して文字を表現します。すべてのASCII文字と一般的に使用される非英語文字は2バイトで表現され、少数の文字は4バイトが必要です。2バイト文字の場合、UTF-16エンコーディングはUnicodeコードポイントと等しくなります。 -- **UTF-32エンコーディング**:すべての文字が4バイトを使用します。これは、UTF-32がUTF-8やUTF-16よりも多くの空間を占有することを意味し、特にASCII文字の割合が高いテキストでは顕著です。 +- **UTF-16 エンコーディング**:1 文字を 2 バイトまたは 4 バイトで表します。すべての ASCII 文字と一般的な非英語文字は 2 バイトで表し、一部の文字だけが 4 バイトを必要とします。2 バイトの文字については、UTF-16 エンコーディングは Unicode コードポイントと等しくなります。 +- **UTF-32 エンコーディング**:各文字を必ず 4 バイトで表します。つまり UTF-32 は UTF-8 や UTF-16 よりも多くの領域を消費し、とくに ASCII 文字の比率が高いテキストでその傾向が顕著です。 -ストレージ空間の観点から、UTF-8を使用して英語文字を表現することは1バイトのみが必要なため非常に効率的です。UTF-16を使用して一部の非英語文字(中国語など)をエンコードすることは、2バイトのみが必要なためより効率的になる場合があります。一方、UTF-8では3バイトが必要になる場合があります。 +記憶領域の使用量という観点では、UTF-8 は英語文字の表現に非常に効率的で、必要なのは 1 バイトだけです。一方、UTF-16 は一部の非英語文字(たとえば中国語の文字)の符号化でより効率的になることがあり、必要なのは 2 バイトだけで、UTF-8 では 3 バイト必要になる場合があります。 -互換性の観点から、UTF-8は最も汎用性があり、多くのツールとライブラリがUTF-8を優先的にサポートしています。 +互換性という観点では、UTF-8 の汎用性が最も高く、多くのツールやライブラリが UTF-8 を優先的にサポートしています。 -## プログラミング言語における文字エンコーディング +## プログラミング言語の文字エンコーディング -歴史的に、多くのプログラミング言語はプログラム実行中の文字列処理にUTF-16やUTF-32などの固定長エンコーディングを利用していました。これにより文字列を配列として処理でき、いくつかの利点があります: +従来の多くのプログラミング言語では、実行中の文字列に UTF-16 や UTF-32 のような固定長エンコーディングが使われています。固定長エンコーディングでは、文字列を配列のように扱えるため、次のような利点があります。 -- **ランダムアクセス**:UTF-16でエンコードされた文字列は簡単にランダムアクセスできます。可変長エンコーディングであるUTF-8の場合、$i^{th}$文字の位置を特定するには文字列の開始から$i^{th}$位置まで走査する必要があり、$O(n)$時間がかかります。 -- **文字数カウント**:ランダムアクセスと同様に、UTF-16でエンコードされた文字列の文字数をカウントすることは$O(1)$操作です。しかし、UTF-8でエンコードされた文字列の文字数をカウントするには文字列全体を走査する必要があります。 -- **文字列操作**:分割、連結、挿入、削除などの多くの文字列操作は、UTF-16でエンコードされた文字列で簡単です。これらの操作は一般的に、UTF-8エンコーディングの有効性を確保するためにUTF-8でエンコードされた文字列で追加の計算が必要です。 +- **ランダムアクセス**:UTF-16 で符号化された文字列はランダムアクセスが容易です。UTF-8 は可変長エンコーディングなので、第 $i$ 文字を見つけるには文字列の先頭から第 $i$ 文字まで走査する必要があり、$O(n)$ の時間がかかります。 +- **文字数の計算**:ランダムアクセスと同様に、UTF-16 で符号化された文字列の長さを計算するのも $O(1)$ の操作です。しかし、UTF-8 で符号化された文字列の長さを計算するには、文字列全体を走査する必要があります。 +- **文字列操作**:UTF-16 で符号化された文字列では、多くの文字列操作(分割、連結、挿入、削除など)をより簡単に行えます。UTF-8 で符号化された文字列では、これらの操作を行う際に、無効な UTF-8 エンコーディングを生じさせないための追加計算が通常必要になります。 -プログラミング言語における文字エンコーディングスキームの設計は、様々な要因を含む興味深いトピックです: +実際、プログラミング言語における文字エンコーディング方式の設計は、とても興味深い話題であり、多くの要因が関わっています。 -- Javaの`String`型はUTF-16エンコーディングを使用し、各文字が2バイトを占有します。これは、16ビットがすべての可能な文字を表現するのに十分であるという初期の信念に基づいており、後に間違いであることが証明されました。Unicode標準が16ビットを超えて拡張されると、Javaの文字は「サロゲートペア」として知られる16ビット値のペアで表現される場合があります。 -- JavaScriptとTypeScriptは、Javaと同様の理由でUTF-16エンコーディングを使用します。JavaScriptが1995年にNetscapeによって最初に導入されたとき、Unicodeはまだ初期段階にあり、16ビットエンコーディングはすべてのUnicode文字を表現するのに十分でした。 -- C#はUTF-16エンコーディングを使用し、これは主にMicrosoftによって設計された.NETプラットフォーム、および多くのMicrosoft技術(Windowsオペレーティングシステムを含む)がUTF-16エンコーディングを広範囲に使用しているためです。 +- Java の `String` 型は UTF-16 エンコーディングを使用し、各文字は 2 バイトを占めます。これは Java 言語の設計当初、人々が 16 ビットあればあらゆる文字を表現するのに十分だと考えていたためです。しかし、これは誤った判断でした。その後 Unicode 規格は 16 ビットを超える範囲へ拡張されたため、現在の Java では 1 文字が 16 ビット値の組(「サロゲートペア」)で表されることがあります。 +- JavaScript と TypeScript の文字列が UTF-16 エンコーディングを使う理由も Java と似ています。1995 年に Netscape 社が初めて JavaScript 言語を公開した当時、Unicode はまだ発展初期にあり、16 ビットの符号化で十分すべての Unicode 文字を表せると考えられていました。 +- C# が UTF-16 エンコーディングを使う主な理由は、.NET プラットフォームが Microsoft によって設計され、Microsoft の多くの技術(Windows オペレーティングシステムを含む)で UTF-16 エンコーディングが広く使われているためです。 -文字数の過小評価により、これらの言語は16ビットを超えるUnicode文字を表現するために「サロゲートペア」を使用する必要がありました。このアプローチには欠点があります:サロゲートペアを含む文字列は2バイトまたは4バイトを占有する文字を持つ場合があり、固定長エンコーディングの利点を失います。さらに、サロゲートペアの処理はプログラミングに複雑さとデバッグの困難さを追加します。 +以上のプログラミング言語は文字数を過小評価していたため、16 ビットを超える長さの Unicode 文字を表すために「サロゲートペア」を採用せざるを得ませんでした。これはやむを得ない妥協策です。一方では、サロゲートペアを含む文字列では、1 文字が 2 バイトまたは 4 バイトを占める可能性があり、固定長エンコーディングの利点が失われます。もう一方では、サロゲートペアの処理には追加のコードが必要となり、プログラミングの複雑さとデバッグの難しさが増します。 -これらの課題に対処するため、一部の言語は代替エンコーディング戦略を採用しています: +こうした理由から、一部のプログラミング言語では別のエンコーディング方式が採用されました。 -- Pythonの`str`型は、文字のストレージ長が文字列内の最大のUnicodeコードポイントに依存する柔軟な表現でUnicodeエンコーディングを使用します。すべての文字がASCIIの場合、各文字は1バイトを占有し、基本多言語面(BMP)内の文字は2バイト、BMPを超える文字は4バイトを占有します。 -- Goの`string`型は内部的にUTF-8エンコーディングを使用します。Goは個別のUnicodeコードポイントを表現するための`rune`型も提供します。 -- Rustの`str`と`String`型は内部的にUTF-8エンコーディングを使用します。Rustは個別のUnicodeコードポイント用の`char`型も提供します。 +- Python の `str` は Unicode エンコーディングを使用し、柔軟な文字列表現を採用しています。保存される文字の長さは、その文字列中で最大の Unicode コードポイントに依存します。文字列がすべて ASCII 文字であれば各文字は 1 バイト、ASCII の範囲を超える文字があってもすべてが基本多言語面(BMP)内であれば各文字は 2 バイト、BMP を超える文字があれば各文字は 4 バイトを占めます。 +- Go 言語の `string` 型は内部で UTF-8 エンコーディングを使用します。Go 言語には単一の Unicode コードポイントを表す `rune` 型も用意されています。 +- Rust 言語の `str` と `String` 型は内部で UTF-8 エンコーディングを使用します。Rust にも単一の Unicode コードポイントを表す `char` 型があります。 -上記の議論は、プログラミング言語での文字列の格納方法に関するものであり、**ファイルでの文字列の格納方法やネットワーク上での送信方法とは異なる**ことに注意することが重要です。ファイルストレージやネットワーク送信では、文字列は通常、最適な互換性と空間効率のためにUTF-8形式でエンコードされます。 +注意すべきなのは、ここまでの議論はすべて、プログラミング言語内での文字列の保存方法についてであり、**文字列をファイルに保存したりネットワークで転送したりする方法とは別の問題である**ということです。ファイル保存やネットワーク転送では、通常、互換性と空間効率を最適化するために文字列を UTF-8 形式にエンコードします。 diff --git a/ja/docs/chapter_data_structure/classification_of_data_structure.md b/ja/docs/chapter_data_structure/classification_of_data_structure.md index 8921aad29..412e787fa 100644 --- a/ja/docs/chapter_data_structure/classification_of_data_structure.md +++ b/ja/docs/chapter_data_structure/classification_of_data_structure.md @@ -1,48 +1,48 @@ # データ構造の分類 -一般的なデータ構造には、配列、連結リスト、スタック、キュー、ハッシュ表、木、ヒープ、グラフがあります。これらは「論理構造」と「物理構造」に分類できます。 +代表的なデータ構造には、配列、連結リスト、スタック、キュー、ハッシュテーブル、木、ヒープ、グラフがあり、これらは「論理構造」と「物理構造」の 2 つの観点から分類できます。 ## 論理構造:線形と非線形 -**論理構造はデータ要素間の論理的関係を明らかにします**。配列と連結リストでは、データは特定の順序で配置され、データ間の線形関係を示しています。一方、木では、データは上から下へ階層的に配置され、「祖先」と「子孫」間の派生関係を示しています。そして、グラフはノードとエッジから構成され、複雑なネットワーク関係を反映しています。 +**論理構造はデータ要素間の論理的な関係を示します**。配列と連結リストでは、データは一定の順序で並び、データ間の線形関係を表します。一方、木ではデータは上から下へ階層的に並び、「祖先」と「子孫」の派生関係を示します。グラフはノードと辺で構成され、複雑なネットワーク関係を反映します。 -下図に示されているように、論理構造は「線形」と「非線形」の2つの主要カテゴリに分けることができます。線形構造はより直感的で、データが論理関係において線形に配置されていることを示しています。非線形構造は、逆に非線形に配置されています。 +以下の図に示すように、論理構造は「線形」と「非線形」の 2 つに大別できます。線形構造は比較的直感的で、データが論理関係において線形に並ぶことを指します。非線形構造はその逆で、非線形に配置されます。 -- **線形データ構造**:配列、連結リスト、スタック、キュー、ハッシュ表。要素が一対一の順次関係を持ちます。 -- **非線形データ構造**:木、ヒープ、グラフ、ハッシュ表。 +- **線形データ構造**:配列、連結リスト、スタック、キュー、ハッシュテーブルであり、要素間は 1 対 1 の順序関係です。 +- **非線形データ構造**:木、ヒープ、グラフ、ハッシュテーブル。 -非線形データ構造は、さらに木構造とネットワーク構造に分けることができます。 +非線形データ構造は、さらに木構造と網状構造に分けられます。 -- **木構造**:木、ヒープ、ハッシュ表。要素が一対多の関係を持ちます。 -- **ネットワーク構造**:グラフ。要素が多対多の関係を持ちます。 +- **木構造**:木、ヒープ、ハッシュテーブルであり、要素間は 1 対多の関係です。 +- **網状構造**:グラフであり、要素間は多対多の関係です。 -![Linear and non-linear data structures](classification_of_data_structure.assets/classification_logic_structure.png) +![線形データ構造と非線形データ構造](classification_of_data_structure.assets/classification_logic_structure.png) ## 物理構造:連続と分散 -**アルゴリズムの実行中、処理されるデータはメモリに格納されます**。下図はコンピュータのメモリスティックを示しており、各黒い正方形は物理メモリ空間です。メモリを巨大なExcelスプレッドシートと考えることができ、各セルは一定量のデータを格納できます。 +**アルゴリズムのプログラムが実行されるとき、処理中のデータは主にメモリに格納されます**。下図はコンピュータのメモリモジュールを示しており、各黒い四角はそれぞれ 1 つのメモリ空間を表しています。メモリは巨大な Excel の表のようなものだと考えることができ、各セルには一定量のデータを格納できます。 -**システムはメモリアドレスによって目標位置のデータにアクセスします**。下図に示されているように、コンピュータは特定のルールに従って表の各セルに一意の識別子を割り当て、各メモリ空間が一意のメモリアドレスを持つことを保証します。これらのアドレスにより、プログラムはメモリに格納されたデータにアクセスできます。 +**システムはメモリアドレスを通じて目的の位置にあるデータへアクセスします**。下図に示すように、コンピュータは特定の規則に従って表内の各セルに番号を割り当て、各メモリ空間が一意のメモリアドレスを持つようにします。これらのアドレスがあれば、プログラムはメモリ内のデータにアクセスできます。 -![Memory stick, memory spaces, memory addresses](classification_of_data_structure.assets/computer_memory_location.png) +![メモリモジュール、メモリ空間、メモリアドレス](classification_of_data_structure.assets/computer_memory_location.png) !!! tip - メモリをExcelスプレッドシートに比較することは簡略化された類推であることに注意してください。メモリの実際の動作メカニズムはより複雑で、アドレス空間、メモリ管理、キャッシュメカニズム、仮想メモリ、物理メモリなどの概念が関係しています。 + 補足すると、メモリを Excel の表にたとえるのは単純化した比喩であり、実際のメモリの動作機構はより複雑で、アドレス空間、メモリ管理、キャッシュ機構、仮想メモリ、物理メモリなどの概念が関わります。 -メモリはすべてのプログラムの共有リソースです。あるメモリブロックが1つのプログラムによって占有されると、他のプログラムが同時に使用することはできません。**したがって、メモリリソースはデータ構造とアルゴリズムの設計における重要な考慮事項です**。例えば、アルゴリズムのピークメモリ使用量は、システムの残り空きメモリを超えてはいけません。連続したメモリブロックが不足している場合は、非連続メモリブロックに格納できるデータ構造を選択する必要があります。 +メモリはすべてのプログラムで共有される資源であり、あるメモリ領域が 1 つのプログラムに占有されると、通常は他のプログラムが同時に利用できません。**したがって、データ構造とアルゴリズムの設計では、メモリ資源は重要な考慮要素です**。たとえば、アルゴリズムが使用するメモリ使用量のピークは、システムに残っている空きメモリを超えてはなりません。大きな連続メモリ領域が不足している場合、選択するデータ構造は分散したメモリ空間に格納できる必要があります。 -下図に示されているように、**物理構造はコンピュータメモリにおけるデータの格納方法を反映し**、連続空間格納(配列)と非連続空間格納(連結リスト)に分けることができます。2つのタイプの物理構造は、時間効率と空間効率の観点で補完的な特性を示します。 +下図に示すように、**物理構造はデータがコンピュータメモリ内にどのように格納されるかを表します**。これは連続空間への格納(配列)と分散空間への格納(連結リスト)に分けられます。物理構造は低レベルでデータのアクセス、更新、追加、削除などの操作方法を決定し、2 種類の物理構造は時間効率と空間効率の面で相補的な特徴を持ちます。 -![Contiguous space storage and dispersed space storage](classification_of_data_structure.assets/classification_phisical_structure.png) +![連続空間格納と分散空間格納](classification_of_data_structure.assets/classification_phisical_structure.png) -**すべてのデータ構造は配列、連結リスト、またはその組み合わせに基づいて実装されていることに注意してください**。例えば、スタックとキューは配列または連結リストのどちらでも実装できます。ハッシュ表の実装には配列と連結リストの両方が関係する場合があります。 +補足すると、**すべてのデータ構造は配列、連結リスト、またはその両者の組み合わせに基づいて実装されます**。たとえば、スタックとキューは配列でも連結リストでも実装できます。一方、ハッシュテーブルの実装には配列と連結リストの両方が含まれる場合があります。 -- **配列ベースの実装**:スタック、キュー、ハッシュ表、木、ヒープ、グラフ、行列、テンソル(次元$\geq 3$の配列)。 -- **連結リストベースの実装**:スタック、キュー、ハッシュ表、木、ヒープ、グラフなど。 +- **配列に基づいて実装可能**:スタック、キュー、ハッシュテーブル、木、ヒープ、グラフ、行列、テンソル(次元 $\geq 3$ の配列)など。 +- **連結リストに基づいて実装可能**:スタック、キュー、ハッシュテーブル、木、ヒープ、グラフなど。 -配列に基づいて実装されたデータ構造は「静的データ構造」とも呼ばれ、初期化後に長さを変更できないことを意味します。逆に、連結リストに基づいたものは「動的データ構造」と呼ばれ、プログラム実行中にサイズを調整できます。 +連結リストは初期化後も、プログラムの実行中に長さを調整できるため、「動的データ構造」とも呼ばれます。配列は初期化後に長さを変更できないため、「静的データ構造」とも呼ばれます。なお、配列もメモリを再割り当てすることで長さを変更でき、ある程度の「動的性」を持たせることができます。 !!! tip - 物理構造を理解するのが困難な場合は、次の章「配列と連結リスト」を読んでから、この節に戻ることをお勧めします。 + 物理構造の理解が難しいと感じる場合は、先に次の章を読んでから本節を振り返ることを勧めます。 diff --git a/ja/docs/chapter_data_structure/index.md b/ja/docs/chapter_data_structure/index.md index feaa3d56a..f52e0f5b6 100644 --- a/ja/docs/chapter_data_structure/index.md +++ b/ja/docs/chapter_data_structure/index.md @@ -1,9 +1,9 @@ # データ構造 -![Data structures](../assets/covers/chapter_data_structure.jpg) +![データ構造](../assets/covers/chapter_data_structure.jpg) !!! abstract - データ構造は堅牢で多様なフレームワークとして機能します。 - - データの整然とした組織化のための設計図を提供し、その上でアルゴリズムが生き生きと動き出します。 + データ構造は、堅固で多様な枠組みのようなものである。 + + それはデータを秩序立てて組織するための青写真を示し、アルゴリズムはその上で生き生きと動き出す。 diff --git a/ja/docs/chapter_data_structure/number_encoding.md b/ja/docs/chapter_data_structure/number_encoding.md index 62bebca8c..b3c4622e8 100644 --- a/ja/docs/chapter_data_structure/number_encoding.md +++ b/ja/docs/chapter_data_structure/number_encoding.md @@ -2,23 +2,23 @@ !!! tip - 本書では、アスタリスク「*」が付いた章は任意読書です。時間が不足している場合や難しいと感じる場合は、最初はこれらをスキップして、必須の章を完了した後に戻ることができます。 + 本書では、タイトルに * 記号が付いている章は選読です。時間が限られている場合や理解が難しいと感じる場合は、いったん読み飛ばし、必読章を終えてから個別に取り組んでください。 -## 整数エンコーディング +## 符号付き絶対値表現、1 の補数、2 の補数 -前の節の表で、すべての整数型は正の数よりも1つ多い負の数を表現できることを観察しました。例えば、`byte`の範囲は$[-128, 127]$です。この現象は直感に反するように見え、その根本的な理由には符号絶対値、1の補数、2の補数エンコーディングの知識が関与しています。 +前節の表を見ると、すべての整数型で表せる負数の個数は正数より 1 つ多く、たとえば `byte` の値域は $[-128, 127]$ です。この現象は直感に反するように見えますが、その背景には符号付き絶対値表現、1 の補数、2 の補数に関する知識があります。 -まず重要なことは、**数値はコンピュータ内で2の補数形式で格納される**ということです。なぜそうなのかを分析する前に、これら3つのエンコーディング方法を定義しましょう: +まず押さえておくべきなのは、**数値はコンピュータ内で「2 の補数」の形で保存される**ということです。その理由を説明する前に、まずはこの 3 つの定義を示します。 -- **符号絶対値**:数値の二進表現の最上位ビットを符号ビットとし、$0$は正の数、$1$は負の数を表します。残りのビットは数値の値を表します。 -- **1の補数**:正の数の1の補数は符号絶対値と同じです。負の数の場合、符号ビット以外のすべてのビットを反転して得られます。 -- **2の補数**:正の数の2の補数は符号絶対値と同じです。負の数の場合、その1の補数に$1$を加えて得られます。 +- **符号付き絶対値表現**:数値の二進表現の最上位ビットを符号ビットとみなし、$0$ は正数、$1$ は負数を表し、残りのビットが数値の値を表します。 +- **1 の補数**:正数の 1 の補数は符号付き絶対値表現と同じで、負数の 1 の補数は符号ビットを除くすべてのビットを反転したものです。 +- **2 の補数**:正数の 2 の補数は符号付き絶対値表現と同じで、負数の 2 の補数は 1 の補数に $1$ を加えたものです。 -以下の図は、符号絶対値、1の補数、2の補数間の変換を示しています: +下図は、符号付き絶対値表現、1 の補数、2 の補数の変換方法を示しています。 -![符号絶対値、1の補数、2の補数間の変換](number_encoding.assets/1s_2s_complement.png) +![符号付き絶対値表現、1 の補数、2 の補数の相互変換](number_encoding.assets/1s_2s_complement.png) -符号絶対値は最も直感的ですが、制限があります。一つには、**符号絶対値の負の数は計算で直接使用できません**。例えば、符号絶対値で$1 + (-2)$を計算すると$-3$になり、これは正しくありません。 +符号付き絶対値表現(sign-magnitude)は最も直感的ですが、いくつかの制約があります。まず、**負数の符号付き絶対値表現はそのまま演算に使えません**。たとえば符号付き絶対値表現で $1 + (-2)$ を計算すると、結果は $-3$ になってしまい、これは明らかに誤りです。 $$ \begin{aligned} @@ -29,20 +29,20 @@ $$ \end{aligned} $$ -この問題に対処するため、コンピュータは1の補数を導入しました。1の補数に変換して$1 + (-2)$を計算し、結果を符号絶対値に戻すと、正しい結果$-1$が得られます。 +この問題を解決するために、コンピュータには1 の補数(1's complement)が導入されました。まず符号付き絶対値表現を 1 の補数に変換し、1 の補数で $1 + (-2)$ を計算してから、結果を 1 の補数から符号付き絶対値表現へ戻すと、正しい結果 $-1$ が得られます。 $$ \begin{aligned} & 1 + (-2) \newline -& \rightarrow 0000 \; 0001 \; \text{(符号絶対値)} + 1000 \; 0010 \; \text{(符号絶対値)} \newline -& = 0000 \; 0001 \; \text{(1の補数)} + 1111 \; 1101 \; \text{(1の補数)} \newline -& = 1111 \; 1110 \; \text{(1の補数)} \newline -& = 1000 \; 0001 \; \text{(符号絶対値)} \newline +& \rightarrow 0000 \; 0001 \; \text{(符号付き絶対値表現)} + 1000 \; 0010 \; \text{(符号付き絶対値表現)} \newline +& = 0000 \; 0001 \; \text{(1 の補数)} + 1111 \; 1101 \; \text{(1 の補数)} \newline +& = 1111 \; 1110 \; \text{(1 の補数)} \newline +& = 1000 \; 0001 \; \text{(符号付き絶対値表現)} \newline & \rightarrow -1 \end{aligned} $$ -また、**符号絶対値では0に2つの表現があります**:$+0$と$-0$です。これは0に対して2つの異なる二進エンコーディングがあることを意味し、曖昧さを引き起こす可能性があります。例えば、条件チェックで正と負の0を区別しないと、正しくない結果になる可能性があります。この曖昧さに対処するには追加のチェックが必要で、計算効率が低下する可能性があります。 +一方、**数値 0 の符号付き絶対値表現には $+0$ と $-0$ の 2 つの表し方があります**。つまり、数値 0 に対して異なる 2 つの二進コードが対応しており、これは曖昧さの原因になります。たとえば条件判定で正のゼロと負のゼロを区別しないと、誤った判定結果になる可能性があります。また、この曖昧さを解消しようとすると追加の判定処理が必要になり、計算効率が下がるおそれがあります。 $$ \begin{aligned} @@ -51,100 +51,100 @@ $$ \end{aligned} $$ -符号絶対値と同様に、1の補数も正と負の0の曖昧さに悩まされます。そのため、コンピュータはさらに2の補数を導入しました。符号絶対値、1の補数、2の補数における負の0の変換過程を観察してみましょう: +符号付き絶対値表現と同様に、1 の補数にも正負のゼロの曖昧さがあります。そこでコンピュータはさらに2 の補数(2's complement)を導入しました。まずは負のゼロについて、符号付き絶対値表現、1 の補数、2 の補数の変換を見てみましょう。 $$ \begin{aligned} --0 \rightarrow \; & 1000 \; 0000 \; \text{(符号絶対値)} \newline -= \; & 1111 \; 1111 \; \text{(1の補数)} \newline -= 1 \; & 0000 \; 0000 \; \text{(2の補数)} \newline +-0 \rightarrow \; & 1000 \; 0000 \; \text{(符号付き絶対値表現)} \newline += \; & 1111 \; 1111 \; \text{(1 の補数)} \newline += 1 \; & 0000 \; 0000 \; \text{(2 の補数)} \newline \end{aligned} $$ -負の0の1の補数に$1$を加えると桁上がりが発生しますが、`byte`の長さは8ビットのみのため、9番目のビットへの桁上がり$1$は破棄されます。したがって、**負の0の2の補数は$0000 \; 0000$**で、正の0と同じになり、曖昧さが解決されます。 +負のゼロの 1 の補数に $1$ を加えると桁上がりが発生しますが、`byte` 型の長さは 8 ビットしかないため、第 9 ビットへあふれた $1$ は捨てられます。つまり、**負のゼロの 2 の補数は $0000 \; 0000$ であり、正のゼロの 2 の補数と同じです**。そのため、2 の補数表現ではゼロは 1 つしか存在せず、正負のゼロの曖昧さは解消されます。 -最後の謎は、`byte`の$[-128, 127]$の範囲で、追加の負の数$-128$があることです。$[-127, +127]$の区間では、すべての整数に対応する符号絶対値、1の補数、2の補数があり、相互変換が可能であることを観察します。 +最後にもう 1 つ疑問が残ります。`byte` 型の値域は $[-128, 127]$ ですが、余分にある負数 $-128$ はどのように得られるのでしょうか。区間 $[-127, +127]$ にあるすべての整数には、それぞれ対応する符号付き絶対値表現、1 の補数、2 の補数があり、符号付き絶対値表現と 2 の補数の間は相互に変換できます。 -しかし、**2の補数$1000 \; 0000$は対応する符号絶対値を持たない例外です**。変換方法によると、その符号絶対値は$0000 \; 0000$で、0を示します。これは矛盾を示しています。なぜなら、その2の補数は自分自身を表すべきだからです。コンピュータは、この特別な2の補数$1000 \; 0000$を$-128$を表すものとして指定しています。実際、2の補数での$(-1) + (-127)$の計算結果は$-128$になります。 +しかし、**2 の補数 $1000 \; 0000$ だけは例外で、対応する符号付き絶対値表現を持ちません**。変換規則に従うと、この 2 の補数に対応する符号付き絶対値表現は $0000 \; 0000$ になります。これは明らかに矛盾しています。なぜなら、この符号付き絶対値表現は数値 $0$ を表し、その 2 の補数は自分自身であるはずだからです。コンピュータでは、この特別な 2 の補数 $1000 \; 0000$ を $-128$ と定めています。実際、2 の補数での $(-1) + (-127)$ の計算結果はちょうど $-128$ になります。 $$ \begin{aligned} & (-127) + (-1) \newline -& \rightarrow 1111 \; 1111 \; \text{(符号絶対値)} + 1000 \; 0001 \; \text{(符号絶対値)} \newline -& = 1000 \; 0000 \; \text{(1の補数)} + 1111 \; 1110 \; \text{(1の補数)} \newline -& = 1000 \; 0001 \; \text{(2の補数)} + 1111 \; 1111 \; \text{(2の補数)} \newline -& = 1000 \; 0000 \; \text{(2の補数)} \newline +& \rightarrow 1111 \; 1111 \; \text{(符号付き絶対値表現)} + 1000 \; 0001 \; \text{(符号付き絶対値表現)} \newline +& = 1000 \; 0000 \; \text{(1 の補数)} + 1111 \; 1110 \; \text{(1 の補数)} \newline +& = 1000 \; 0001 \; \text{(2 の補数)} + 1111 \; 1111 \; \text{(2 の補数)} \newline +& = 1000 \; 0000 \; \text{(2 の補数)} \newline & \rightarrow -128 \end{aligned} $$ -お気づきかもしれませんが、これらの計算はすべて加算であり、重要な事実を示唆しています:**コンピュータの内部ハードウェア回路は主に加算演算を中心に設計されています**。これは、加算が乗算、除算、減算などの他の演算と比較してハードウェアで実装しやすく、並列化が容易で高速計算が可能だからです。 +すでにお気づきかもしれませんが、上の計算はすべて加算です。これは重要な事実を示しています。**コンピュータ内部のハードウェア回路は、主として加算を基準に設計されている**のです。なぜなら、加算はほかの演算(乗算、除算、減算など)に比べてハードウェアで実装しやすく、並列化もしやすく、演算速度も速いからです。 -これはコンピュータが加算のみを実行できることを意味するものではありません。**加算と基本的な論理演算を組み合わせることで、コンピュータは様々な他の数学演算を実行できます**。例えば、減算$a - b$は$a + (-b)$に変換でき、乗算と除算は複数の加算または減算に変換できます。 +ただし、これはコンピュータが加算しかできないという意味ではありません。**加算といくつかの基本的な論理演算を組み合わせることで、コンピュータはさまざまな数学演算を実現できます**。たとえば減算 $a - b$ は加算 $a + (-b)$ に変換できますし、乗算や除算も繰り返しの加算または減算に変換できます。 -コンピュータで2の補数を使用する理由をまとめることができます:2の補数表現により、コンピュータは同じ回路と演算を使用して正と負の数の加算を処理でき、減算用の特別なハードウェア回路の必要性を排除し、正と負の0の曖昧さを回避できます。これによりハードウェア設計が大幅に簡素化され、計算効率が向上します。 +これで、コンピュータが 2 の補数を使う理由をまとめられます。2 の補数表現に基づけば、コンピュータは同じ回路と操作で正数と負数の加算を扱うことができ、減算専用の特別なハードウェア回路を設計する必要がなく、正負のゼロの曖昧さも特別に処理しなくて済みます。これにより、ハードウェア設計は大幅に簡略化され、演算効率も向上します。 -2の補数の設計は非常に巧妙で、スペースの制約により、ここで停止します。興味のある読者はさらに探求することを奨励します。 +2 の補数の設計は非常に巧妙ですが、紙幅の都合上ここまでにします。興味のある読者は、さらに深く調べてみてください。 -## 浮動小数点数エンコーディング +## 浮動小数点数のエンコーディング -興味深いことに気づいたかもしれません:同じ4バイトの長さにもかかわらず、なぜ`float`は`int`と比較してはるかに大きい値の範囲を持つのでしょうか?これは直感に反するように見えます。`float`は分数を表現する必要があるため、範囲が縮小すると予想されるからです。 +注意深い人なら気づくかもしれません。`int` と `float` はどちらも長さが 4 バイトで同じなのに、なぜ `float` の値域は `int` よりはるかに広いのでしょうか。これはかなり直感に反します。というのも、`float` は小数を表す必要があるので、本来なら値域は狭くなるはずだからです。 -実際、**これは浮動小数点数(`float`)で使用される異なる表現方法によるものです**。32ビットの二進数を次のように考えてみましょう: +実際には、**これは浮動小数点数 `float` が異なる表現方法を採用しているためです**。32 ビット長の二進数を次のように表します。 $$ b_{31} b_{30} b_{29} \ldots b_2 b_1 b_0 $$ -IEEE 754標準によると、32ビットの`float`は次の3つの部分で構成されます: +IEEE 754 標準によれば、32-bit 長の `float` は次の 3 つの部分から構成されます。 -- 符号ビット$\mathrm{S}$:1ビットを占有し、$b_{31}$に対応します。 -- 指数ビット$\mathrm{E}$:8ビットを占有し、$b_{30} b_{29} \ldots b_{23}$に対応します。 -- 仮数ビット$\mathrm{N}$:23ビットを占有し、$b_{22} b_{21} \ldots b_0$に対応します。 +- 符号部 $\mathrm{S}$ :1 ビットを占め、$b_{31}$ に対応します。 +- 指数部 $\mathrm{E}$ :8 ビットを占め、$b_{30} b_{29} \ldots b_{23}$ に対応します。 +- 仮数部 $\mathrm{N}$ :23 ビットを占め、$b_{22} b_{21} \ldots b_0$ に対応します。 -二進`float`数の値は次のように計算されます: +二進数 `float` に対応する値は次式で計算されます。 $$ -\text{val} = (-1)^{b_{31}} \times 2^{\left(b_{30} b_{29} \ldots b_{23}\right)_2 - 127} \times \left(1 . b_{22} b_{21} \ldots b_0\right)_2 +\text {val} = (-1)^{b_{31}} \times 2^{\left(b_{30} b_{29} \ldots b_{23}\right)_2-127} \times\left(1 . b_{22} b_{21} \ldots b_0\right)_2 $$ -十進公式に変換すると、次のようになります: +十進数に直すと、計算式は次のようになります。 $$ -\text{val} = (-1)^{\mathrm{S}} \times 2^{\mathrm{E} - 127} \times (1 + \mathrm{N}) +\text {val}=(-1)^{\mathrm{S}} \times 2^{\mathrm{E} -127} \times (1 + \mathrm{N}) $$ -各成分の範囲は: +各項の取り得る範囲は次のとおりです。 $$ \begin{aligned} \mathrm{S} \in & \{ 0, 1\}, \quad \mathrm{E} \in \{ 1, 2, \dots, 254 \} \newline -(1 + \mathrm{N}) = & (1 + \sum_{i=1}^{23} b_{23-i} \times 2^{-i}) \subset [1, 2 - 2^{-23}] +(1 + \mathrm{N}) = & (1 + \sum_{i=1}^{23} b_{23-i} 2^{-i}) \subset [1, 2 - 2^{-23}] \end{aligned} $$ -![IEEE 754標準での浮動小数点数の計算例](number_encoding.assets/ieee_754_float.png) +![IEEE 754 標準における float の計算例](number_encoding.assets/ieee_754_float.png) -上の図を観察すると、例のデータ$\mathrm{S} = 0$、$\mathrm{E} = 124$、$\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$が与えられた場合: +上図を見ると、例として $\mathrm{S} = 0$ 、 $\mathrm{E} = 124$ 、$\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$ が与えられた場合、次のようになります。 $$ -\text{val} = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875 +\text { val } = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875 $$ -これで最初の質問に答えることができます:**`float`の表現には指数ビットが含まれているため、`int`よりもはるかに大きい範囲を持ちます**。上記の計算に基づくと、`float`で表現可能な最大正の数は約$2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$で、最小負の数は符号ビットを切り替えることで得られます。 +これで最初の疑問に答えられます。**`float` の表現方法には指数部が含まれているため、その値域は `int` よりはるかに広い**のです。上の計算より、`float` が表せる最大の正数は $2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$ であり、符号ビットを切り替えれば最小の負数が得られます。 -**しかし、`float`の拡張された範囲のトレードオフは精度の犠牲です**。整数型`int`は32ビットすべてを数値表現に使用し、値は均等に分布していますが、指数ビットのため、`float`の値が大きいほど、隣接する数値間の差が大きくなります。 +**浮動小数点数 `float` は値域を広げる一方で、その代償として精度を犠牲にしています**。整数型 `int` は 32 ビットすべてを数値の表現に使うため、数値は一様に分布します。しかし指数部があるため、浮動小数点数 `float` は値が大きくなるほど、隣り合う 2 つの数の差も大きくなる傾向があります。 -以下の表に示すように、指数ビット$\mathrm{E} = 0$と$\mathrm{E} = 255$は特別な意味を持ち、**0、無限大、$\mathrm{NaN}$などを表現するために使用されます**。 +次の表のとおり、指数部 $\mathrm{E} = 0$ と $\mathrm{E} = 255$ には特別な意味があり、**ゼロ、無限大、$\mathrm{NaN}$ などを表すために使われます**。 -

  指数ビットの意味

+

  指数部の意味

-| 指数ビットE | 仮数ビット$\mathrm{N} = 0$ | 仮数ビット$\mathrm{N} \ne 0$ | 計算公式 | -| ------------------ | ----------------------------- | ------------------------------- | ---------------------------------------------------------------------- | -| $0$ | $\pm 0$ | 非正規化数 | $(-1)^{\mathrm{S}} \times 2^{-126} \times (0.\mathrm{N})$ | -| $1, 2, \dots, 254$ | 正規化数 | 正規化数 | $(-1)^{\mathrm{S}} \times 2^{(\mathrm{E} -127)} \times (1.\mathrm{N})$ | -| $255$ | $\pm \infty$ | $\mathrm{NaN}$ | | +| 指数部 E | 仮数部 $\mathrm{N} = 0$ | 仮数部 $\mathrm{N} \ne 0$ | 計算式 | +| ------------------ | ----------------------- | ------------------------- | ---------------------------------------------------------------------- | +| $0$ | $\pm 0$ | 非正規化数 | $(-1)^{\mathrm{S}} \times 2^{-126} \times (0.\mathrm{N})$ | +| $1, 2, \dots, 254$ | 正規化数 | 正規化数 | $(-1)^{\mathrm{S}} \times 2^{(\mathrm{E} -127)} \times (1.\mathrm{N})$ | +| $255$ | $\pm \infty$ | $\mathrm{NaN}$ | | -非正規化数は浮動小数点数の精度を大幅に向上させることは注目に値します。最小の正の正規化数は$2^{-126}$で、最小の正の非正規化数は$2^{-126} \times 2^{-23}$です。 +なお、非正規化数によって浮動小数点数の精度は大きく向上します。最小の正の正規化数は $2^{-126}$ であり、最小の正の非正規化数は $2^{-126} \times 2^{-23}$ です。 -倍精度`double`も`float`と同様の表現方法を使用しますが、簡潔さのためここでは詳述しません。 +倍精度 `double` も `float` と同様の表現方法を採用しているため、ここでは詳述しません。 diff --git a/ja/docs/chapter_data_structure/summary.md b/ja/docs/chapter_data_structure/summary.md index 1c079e5c5..b42ce6cfe 100644 --- a/ja/docs/chapter_data_structure/summary.md +++ b/ja/docs/chapter_data_structure/summary.md @@ -1,66 +1,66 @@ # まとめ -### 重要なポイント +### 重要ポイントの振り返り -- データ構造は論理構造と物理構造の2つの観点から分類できます。論理構造はデータ間の論理的関係を記述し、物理構造はデータがメモリにどのように格納されるかを記述します。 -- よく使用される論理構造には、線形構造、木、ネットワークがあります。通常、論理構造に基づいてデータ構造を線形(配列、連結リスト、スタック、キュー)と非線形(木、グラフ、ヒープ)に分けます。ハッシュ表の実装は線形と非線形の両方のデータ構造を含む場合があります。 -- プログラムが実行中の際、データはメモリに格納されます。各メモリ空間には対応するアドレスがあり、プログラムはこれらのアドレスを通じてデータにアクセスします。 -- 物理構造は連続空間格納(配列)と離散空間格納(連結リスト)に分けることができます。すべてのデータ構造は配列、連結リスト、またはその両方の組み合わせを使用して実装されます。 -- コンピュータの基本データ型には、整数(`byte`、`short`、`int`、`long`)、浮動小数点数(`float`、`double`)、文字(`char`)、ブール値(`bool`)が含まれます。データ型の値の範囲は、そのサイズと表現に依存します。 -- 符号絶対値、1の補数、2の補数は、コンピュータで整数をエンコードする3つの方法であり、相互に変換することができます。符号絶対値の最上位ビットは符号ビットで、残りのビットは数値の値を表します。 -- 整数はコンピュータで2の補数によってエンコードされます。この表現の利点には、(i)コンピュータが正と負の整数の加算を統一できる、(ii)減算用の特別なハードウェア回路を設計する必要がない、(iii)正と負の0の曖昧さがない、があります。 -- 浮動小数点数のエンコーディングは、1つの符号ビット、8つの指数ビット、23の仮数ビットで構成されます。指数ビットのため、浮動小数点数の範囲は整数よりもはるかに大きくなりますが、精度を犠牲にします。 -- ASCIIは最初期の英語文字セットで、1バイトの長さで計127文字です。GBKは人気のある中国語文字セットで、2万文字以上の中国語文字を含みます。Unicodeは世界の様々な言語の文字を含む完全な文字セット標準を提供することを目的とし、文字エンコーディング方法の不一致による文字化け問題を解決します。 -- UTF-8は最も人気があり一般的なUnicodeエンコーディング方法です。これは可変長エンコーディング方法で、優れた拡張性と空間効率を持ちます。UTF-16とUTF-32は固定長エンコーディング方法です。中国語文字をエンコードする際、UTF-16はUTF-8よりも少ない空間を使用します。JavaやC#などのプログラミング言語はデフォルトでUTF-16エンコーディングを使用します。 +- データ構造は、論理構造と物理構造という 2 つの観点から分類できます。論理構造はデータ要素間の論理的関係を記述し、物理構造はデータのコンピュータメモリ上での格納方法を記述します。 +- 代表的な論理構造には、線形、木構造、網状構造などがあります。通常、論理構造に基づいてデータ構造を線形(配列、連結リスト、スタック、キュー)と非線形(木、グラフ、ヒープ)の 2 種類に分類します。ハッシュテーブルの実装には、線形データ構造と非線形データ構造が同時に含まれる場合があります。 +- プログラムの実行時、データはコンピュータメモリに格納されます。各メモリ空間には対応するメモリアドレスがあり、プログラムはそれらのメモリアドレスを通じてデータにアクセスします。 +- 物理構造は主に連続領域への格納(配列)と分散領域への格納(連結リスト)に分けられます。すべてのデータ構造は、配列、連結リスト、またはその両方の組み合わせによって実装されます。 +- コンピュータにおける基本データ型には、整数 `byte`、`short`、`int`、`long`、浮動小数点数 `float`、`double`、文字 `char`、真偽値 `bool` があります。これらの値域は、使用する記憶領域の大きさと表現方式によって決まります。 +- 符号付き絶対値表現、1 の補数、2 の補数は、コンピュータで数値を符号化する 3 つの方法であり、相互に変換できます。整数の符号付き絶対値表現では最上位ビットが符号ビットで、残りのビットが数値の値です。 +- 整数はコンピュータ内では 2 の補数の形式で格納されます。2 の補数表現では、コンピュータは正数と負数の加算を同じように扱うことができ、減算のために特別なハードウェア回路を別途設計する必要がなく、さらに正負のゼロが重複する問題もありません。 +- 浮動小数点数の符号化は、1 ビットの符号部、8 ビットの指数部、23 ビットの仮数部で構成されます。指数部があるため、浮動小数点数の値域は整数よりはるかに広くなりますが、その代償として精度が犠牲になります。 +- ASCII コードは最も早く登場した英字文字集合で、長さは 1 バイト、収録文字数は 127 です。GBK 文字集合はよく使われる中国語文字集合で、2 万字以上の漢字を収録しています。Unicode は完全な文字集合標準を提供することを目指しており、世界中のさまざまな言語の文字を収録することで、文字コード方式の不一致によって生じる文字化けの問題を解決します。 +- UTF-8 は最も広く使われている Unicode の符号化方式で、汎用性が非常に高いです。可変長の符号化方式であり、拡張性に優れ、記憶領域の利用効率を効果的に高めます。UTF-16 と UTF-32 は固定長の符号化方式です。中国語を符号化する場合、UTF-16 は UTF-8 よりも使用領域が小さくなります。Java や C# などのプログラミング言語は、デフォルトで UTF-16 を使用します。 ### Q & A -**Q**: なぜハッシュ表は線形と非線形の両方のデータ構造を含むのですか? +**Q**:なぜハッシュテーブルには線形データ構造と非線形データ構造が同時に含まれるのですか? -ハッシュ表の基礎構造は配列です。ハッシュ衝突を解決するために、「チェイン法」を使用する場合があります(後の節「ハッシュ衝突」で説明):配列の各バケットは連結リストを指し、その長さが特定の閾値より大きくなると木(通常は赤黒木)に変換される可能性があります。 -格納の観点から、ハッシュ表の基礎構造は配列で、各バケットには値、連結リスト、または木が含まれる場合があります。したがって、ハッシュ表は線形データ構造(配列、連結リスト)と非線形データ構造(木)の両方を含む場合があります。 +ハッシュテーブルの基盤は配列であり、ハッシュ衝突を解決するために「チェイン法」(後続の「ハッシュ衝突」の章で説明します)を使うことがあります。配列内の各バケットは 1 つの連結リストを指し、その連結リストの長さがある閾値を超えると、木(通常は赤黒木)に変換されることもあります。 -**Q**: `char`型の長さは1バイトですか? +格納の観点から見ると、ハッシュテーブルの基盤は配列であり、各バケットスロットには値が入ることもあれば、連結リストや木が入ることもあります。したがって、ハッシュテーブルには線形データ構造(配列、連結リスト)と非線形データ構造(木)が同時に含まれる場合があります。 -`char`型の長さは、プログラミング言語のエンコーディング方法によって決まります。例えば、Java、JavaScript、TypeScript、C#はすべてUTF-16エンコーディング(Unicodeコードポイントを保存するため)を使用するため、`char`型の長さは2バイトです。 +**Q**:`char` 型の長さは 1 バイトですか? -**Q**: 配列ベースのデータ構造を「静的データ構造」と呼ぶことに曖昧さはありませんか?スタックもプッシュやポップなどの「動的」操作を実行できます。 +`char` 型の長さは、プログラミング言語が採用する符号化方式によって決まります。たとえば、Java、JavaScript、TypeScript、C# はいずれも UTF-16 符号化(Unicode コードポイントを保持)を採用しているため、`char` 型の長さは 2 バイトです。 -スタックは動的なデータ操作を実装できますが、データ構造は依然として「静的」です(長さが固定)。配列ベースのデータ構造は動的に要素を追加または削除できますが、その容量は固定されています。スタックサイズが事前に割り当てられたサイズを超える場合、古い配列は新しく作成されたより大きな配列にコピーされます。 +**Q**:配列ベースで実装されたデータ構造を「静的データ構造」と呼ぶのは曖昧ではありませんか? スタックも push や pop などの操作ができ、これらの操作はどれも「動的」です。 -**Q**: スタック(キュー)を構築する際、そのサイズが指定されていないのに、なぜ「静的データ構造」なのですか? +スタックは確かに動的なデータ操作を実現できますが、データ構造自体は依然として「静的」(長さが不変)です。配列ベースのデータ構造でも要素を動的に追加または削除できますが、その容量は固定です。データ量が事前に確保した大きさを超えた場合は、より大きな新しい配列を作成し、古い配列の内容を新しい配列にコピーする必要があります。 -高級プログラミング言語では、スタック(キュー)の初期容量を手動で指定する必要はありません。このタスクはクラス内で自動的に完了されます。例えば、Javaの`ArrayList`の初期容量は通常10です。さらに、拡張操作も自動的に完了されます。詳細については、後続の「リスト」の章を参照してください。 +**Q**:スタック(キュー)を構築するときにサイズを指定していないのに、なぜそれらは「静的データ構造」なのですか? -**Q**: 符号絶対値を2の補数に変換する方法は「最初に否定してから1を加える」ですので、2の補数を符号絶対値に変換することはその逆操作「最初に1を減算してから否定する」であるべきです。 -しかし、2の補数も「最初に否定してから1を加える」を通じて符号絶対値に変換できます。なぜですか? +高水準プログラミング言語では、スタック(キュー)の初期容量を人手で指定する必要はなく、この作業はクラス内部で自動的に行われます。たとえば、Java の `ArrayList` の初期容量は通常 10 です。また、容量拡張も自動的に実装されています。詳しくは後続の「リスト」の章を参照してください。 -**A**: これは、符号絶対値と2の補数間の相互変換が「補数」の計算と等価だからです。まず補数を定義します:$a + b = c$と仮定すると、$a$は$b$の$c$に対する補数と言い、逆に$b$は$a$の$c$に対する補数と言います。 +**Q**:符号付き絶対値表現から 2 の補数への変換方法は「先にビット反転してから 1 を加える」ですが、2 の補数から符号付き絶対値表現への変換は逆演算である「先に 1 を引いてからビット反転する」べきなのに、同じく「先にビット反転してから 1 を加える」でも求められます。これはなぜですか? -長さ$n = 4$の二進数$0010$が与えられた場合、この数が符号絶対値(符号ビットを無視)の場合、その2の補数は「最初に否定してから1を加える」ことで得られます: +これは、符号付き絶対値表現と 2 の補数の相互変換が、実際には「補数」を計算する過程だからです。まず補数の定義を示します。$a + b = c$ とすると、$a$ を $b$ から $c$ への補数と呼び、逆に $b$ も $a$ から $c$ への補数と呼びます。 + +長さ $n = 4$ ビットの 2 進数 $0010$ が与えられたとします。この数を符号付き絶対値表現(符号ビットは考慮しない)とみなすと、その 2 の補数は「先にビット反転してから 1 を加える」ことで得られます。 $$ 0010 \rightarrow 1101 \rightarrow 1110 $$ -符号絶対値と2の補数の和が$0010 + 1110 = 10000$であることを観察します。つまり、2の補数$1110$は符号絶対値$0010$の$10000$に対する「補数」です。**これは、上記の「最初に否定してから1を加える」が$10000$に対する補数の計算と等価であることを意味します**。 +ここで、符号付き絶対値表現と 2 の補数の和は $0010 + 1110 = 10000$ となります。つまり、2 の補数 $1110$ は符号付き絶対値表現 $0010$ から $10000$ への「補数」です。**これは、上記の「先にビット反転してから 1 を加える」が、実際には $10000$ への補数を計算する過程であることを意味します。** -では、$1110$の$10000$に対する「補数」は何でしょうか?「最初に否定してから1を加える」ことで計算できます: +では、2 の補数 $1110$ から $10000$ への「補数」はいくつでしょうか。これもやはり「先にビット反転してから 1 を加える」ことで求められます。 $$ 1110 \rightarrow 0001 \rightarrow 0010 $$ -言い換えると、符号絶対値と2の補数は互いに$10000$に対する「補数」であるため、「符号絶対値から2の補数」と「2の補数から符号絶対値」は同じ操作(最初に否定してから1を加える)で実装できます。 +言い換えると、符号付き絶対値表現と 2 の補数は互いに相手から $10000$ への「補数」なので、「符号付き絶対値表現から 2 の補数への変換」と「2 の補数から符号付き絶対値表現への変換」は同じ操作(先にビット反転してから 1 を加える)で実現できます。 -もちろん、「最初に否定してから1を加える」の逆操作を使用して2の補数$1110$の符号絶対値を求めることもできます。つまり、「最初に1を減算してから否定する」: +もちろん、逆演算を用いて 2 の補数 $1110$ の符号付き絶対値表現を求めることもでき、その場合は「先に 1 を引いてからビット反転する」ことになります。 $$ 1110 \rightarrow 1101 \rightarrow 0010 $$ -要約すると、「最初に否定してから1を加える」と「最初に1を減算してから否定する」は両方とも$10000$に対する補数を計算しており、等価です。 +まとめると、「先にビット反転してから 1 を加える」と「先に 1 を引いてからビット反転する」の 2 つの演算は、どちらも $10000$ への補数を計算しており、等価です。 -本質的に、「否定」操作は実際には$1111$に対する補数を求めることです(`符号絶対値 + 1の補数 = 1111`が常に成り立つため)。そして1の補数に1を加えることは$10000$に対する2の補数と等しくなります。 +本質的には、「ビット反転」という操作は実際には $1111$ への補数を求めています(常に `符号付き絶対値表現 + 1 の補数 = 1111` が成り立つため)。そして、1 の補数にさらに 1 を加えて得られる 2 の補数が、$10000$ への補数です。 -上記では$n = 4$を例に取りましたが、任意の桁数の任意の二進数に一般化できます。 +上記では $n = 4$ を例にしましたが、この考え方は任意のビット長の 2 進数に一般化できます。 diff --git a/ja/docs/chapter_divide_and_conquer/binary_search_recur.md b/ja/docs/chapter_divide_and_conquer/binary_search_recur.md index 89a523daf..459d25ffd 100644 --- a/ja/docs/chapter_divide_and_conquer/binary_search_recur.md +++ b/ja/docs/chapter_divide_and_conquer/binary_search_recur.md @@ -1,44 +1,44 @@ -# 分割統治検索戦略 +# 分割統治探索戦略 -私たちは検索アルゴリズムが主に2つのカテゴリに分類されることを学びました。 +私たちはすでに学んだように、探索アルゴリズムは大きく二つに分けられる。 -- **総当たり検索**:データ構造を走査することで実装され、時間計算量は $O(n)$ です。 -- **適応検索**:独特なデータ組織形式や事前情報を利用し、時間計算量は $O(\log n)$ または $O(1)$ に達することができます。 +- **力ずく探索**:データ構造を走査することで実現され、時間計算量は $O(n)$ である。 +- **適応的探索**:固有のデータ構造や事前情報を利用し、時間計算量は $O(\log n)$ 、さらには $O(1)$ に達しうる。 -実際、**時間計算量が $O(\log n)$ の検索アルゴリズムは通常分割統治戦略に基づいています**。例えば、二分探索や木などです。 +実際、**時間計算量が $O(\log n)$ の探索アルゴリズムは通常、分割統治戦略に基づいて実装される**。たとえば二分探索や木構造である。 -- 二分探索の各ステップは、問題(配列内でターゲット要素を検索する)をより小さな問題(配列の半分でターゲット要素を検索する)に分割し、配列が空になるかターゲット要素が見つかるまで続けます。 -- 木は分割統治のアイデアを表現し、二分探索木、AVL木、ヒープなどのデータ構造では、様々な操作の時間計算量は $O(\log n)$ です。 +- 二分探索の各ステップでは、問題(配列内で目標要素を探索すること)を小さな問題(配列の半分で目標要素を探索すること)に分解し、この過程は配列が空になるか目標要素が見つかるまで続く。 +- 木構造は分割統治の考え方を代表するものであり、二分探索木、AVL 木、ヒープなどのデータ構造では、さまざまな操作の時間計算量はいずれも $O(\log n)$ である。 -二分探索の分割統治戦略は以下の通りです。 +二分探索の分割統治戦略は以下のとおりである。 -- **問題を分割できる**:二分探索は元の問題(配列内での検索)を部分問題(配列の半分での検索)に再帰的に分割し、中間要素とターゲット要素を比較することで実現されます。 -- **部分問題は独立している**:二分探索では、各ラウンドで一つの部分問題を処理し、他の部分問題に影響されません。 -- **部分問題の解をマージする必要がない**:二分探索は特定の要素を見つけることを目的としているため、部分問題の解をマージする必要がありません。部分問題が解決されると、元の問題も解決されます。 +- **問題は分解できる**:二分探索は、元の問題(配列内で探索すること)を部分問題(配列の半分で探索すること)へ再帰的に分解する。これは中央要素と目標要素を比較することで実現される。 +- **部分問題は独立している**:二分探索では、各ラウンドで一つの部分問題だけを処理し、ほかの部分問題の影響を受けない。 +- **部分問題の解を統合する必要はない**:二分探索は特定の要素を探すことを目的としているため、部分問題の解を統合する必要がない。部分問題が解決されると、元の問題も同時に解決される。 -分割統治は検索効率を向上させることができます。なぜなら、総当たり検索はラウンドごとに1つの選択肢しか除去できませんが、**分割統治は選択肢の半分を除去できるからです**。 +分割統治が探索効率を高められる本質的な理由は、力ずく探索では各ラウンドで一つの候補しか除外できないのに対し、**分割統治による探索では各ラウンドで候補の半分を除外できる**からである。 -### 分割統治に基づく二分探索の実装 +### 分割統治に基づく二分探索 -前の章では、二分探索は反復に基づいて実装されました。今度は、分割統治(再帰)に基づいて実装します。 +前の章では、二分探索を漸化式(反復)に基づいて実装した。ここでは分割統治(再帰)に基づいてこれを実装する。 !!! question - 長さ $n$ の順序付けられた配列 `nums` が与えられ、すべての要素が一意である場合、要素 `target` を見つけてください。 + 長さ $n$ の昇順配列 `nums` が与えられ、そのすべての要素は一意である。要素 `target` を探索せよ。 -分割統治の観点から、検索区間 $[i, j]$ に対応する部分問題を $f(i, j)$ と表します。 +分割統治の観点から、探索区間 $[i, j]$ に対応する部分問題を $f(i, j)$ と記す。 -元の問題 $f(0, n-1)$ から開始して、以下のステップで二分探索を実行します。 +元の問題 $f(0, n-1)$ を出発点として、次の手順で二分探索を行う。 -1. 検索区間 $[i, j]$ の中点 $m$ を計算し、それを使用して検索区間の半分を除去します。 -2. 半分のサイズに縮小された部分問題を再帰的に解決します。これは $f(i, m-1)$ または $f(m+1, j)$ になる可能性があります。 -3. `target` が見つかるか区間が空になってリターンするまで、ステップ `1.` と `2.` を繰り返します。 +1. 探索区間 $[i, j]$ の中点 $m$ を計算し、それに基づいて探索区間の半分を除外する。 +2. 規模が半分に縮小された部分問題を再帰的に解く。候補は $f(i, m-1)$ または $f(m+1, j)$ である。 +3. `1.` と `2.` の手順を繰り返し、`target` が見つかるか区間が空になったら返す。 -以下の図は、配列内で要素 $6$ を探す二分探索の分割統治過程を示しています。 +次の図は、配列内で要素 $6$ を二分探索する分割統治の過程を示している。 -![二分探索の分割統治過程](binary_search_recur.assets/binary_search_recur.png) +![二分探索の分割統治の過程](binary_search_recur.assets/binary_search_recur.png) -実装コードでは、問題 $f(i, j)$ を解決するために再帰関数 `dfs()` を宣言します: +実装コードでは、再帰関数 `dfs()` を宣言して問題 $f(i, j)$ を解く。 ```src [file]{binary_search_recur}-[class]{}-[func]{binary_search} diff --git a/ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.md b/ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.md index 3f6990929..c61bd21ce 100644 --- a/ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.md +++ b/ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.md @@ -1,70 +1,70 @@ -# 二分木構築問題 +# 二分木の構築問題 !!! question - 二分木の前順走査 `preorder` シーケンスと中順走査 `inorder` シーケンスが与えられた場合、二分木を構築してそのルートノードを返してください。二分木に重複するノード値がないと仮定します(以下の図に示すように)。 + 二分木の前順走査 `preorder` と中順走査 `inorder` が与えられたとき、これらから二分木を構築し、その根ノードを返してください。二分木には値が重複するノードが存在しないものとします(下図のとおり)。 -![二分木構築のサンプルデータ](build_binary_tree_problem.assets/build_tree_example.png) +![二分木を構築する例のデータ](build_binary_tree_problem.assets/build_tree_example.png) -### 分割統治問題かどうかの判定 +### 分割統治問題かどうかを判断する -`preorder` と `inorder` シーケンスから二分木を構築する元の問題は、典型的な分割統治問題です。 +元の問題は `preorder` と `inorder` から二分木を構築することであり、典型的な分割統治問題です。 -- **問題を分解できる**:分割統治の観点から、元の問題を2つの部分問題(左の部分木の構築と右の部分木の構築)とルートノードの初期化という1つの操作に分割できます。各部分木(部分問題)について、同じアプローチを継続的に適用し、より小さな部分木(部分問題)に分割し、最小の部分問題(空の部分木)に到達するまで続けます。 -- **部分問題は独立している**:左と右の部分木は重複しません。左の部分木を構築する際、左の部分木に対応する中順走査と前順走査のセグメントのみが必要です。右の部分木にも同じアプローチが適用されます。 -- **部分問題の解を組み合わせることができる**:左と右の部分木(部分問題の解)を構築したら、それらをルートノードに接続して元の問題の解を取得できます。 +- **問題は分解できる**:分割統治の観点から見ると、元の問題は 2 つの部分問題、すなわち左部分木の構築と右部分木の構築に分けられ、さらに根ノードを初期化する 1 ステップが加わります。各部分木(部分問題)に対しても、同じ分割方法を再利用してより小さな部分木(部分問題)へと分けていき、最小の部分問題(空部分木)に達した時点で終了します。 +- **部分問題は独立している**:左部分木と右部分木は互いに独立しており、両者の間に重なりはありません。左部分木を構築するときは、中順走査と前順走査のうち左部分木に対応する部分だけを見れば十分です。右部分木も同様です。 +- **部分問題の解は統合できる**:左部分木と右部分木(部分問題の解)が得られたら、それらを根ノードに接続することで元の問題の解を得られます。 -### 部分木の分割方法 +### 部分木をどのように分割するか -上記の分析に基づいて、この問題は分割統治を使用して解決できます。**しかし、前順走査 `preorder` シーケンスと中順走査 `inorder` シーケンスを使用して左と右の部分木をどのように分割すればよいでしょうか?** +以上の分析より、この問題は分割統治で解けます。**では、前順走査 `preorder` と中順走査 `inorder` を使って左部分木と右部分木をどのように分割すればよいのでしょうか**? -定義により、`preorder` と `inorder` シーケンスの両方を3つの部分に分割できます: +定義に従うと、`preorder` と `inorder` はいずれも 3 つの部分に分けられます。 -- 前順走査:`[ ルート | 左の部分木 | 右の部分木 ]`。例えば、図では、木は `[ 3 | 9 | 2 1 7 ]` に対応します。 -- 中順走査:`[ 左の部分木 | ルート | 右の部分木 ]`。例えば、図では、木は `[ 9 | 3 | 1 2 7 ]` に対応します。 +- 前順走査:`[ 根ノード | 左部分木 | 右部分木 ]` ,例えば上図の木は `[ 3 | 9 | 2 1 7 ]` に対応します。 +- 中順走査:`[ 左部分木 | 根ノード | 右部分木 ]` ,例えば上図の木は `[ 9 | 3 | 1 2 7 ]` に対応します。 -前の図のデータを使用して、次の図に示すステップに従って分割結果を取得できます: +上図のデータを例にすると、下図の手順によって分割結果を得られます。 -1. 前順走査の最初の要素3がルートノードの値です。 -2. `inorder` シーケンス内でルートノード3のインデックスを見つけ、このインデックスを使用して `inorder` を `[ 9 | 3 | 1 2 7 ]` に分割します。 -3. `inorder` シーケンスの分割に従って、左と右の部分木がそれぞれ1個と3個のノードを含むことが簡単に決定できるため、`preorder` シーケンスを `[ 3 | 9 | 2 1 7 ]` に対応して分割できます。 +1. 前順走査の先頭要素 3 が根ノードの値です。 +2. 根ノード 3 の `inorder` におけるインデックスを探すと、そのインデックスを用いて `inorder` を `[ 9 | 3 | 1 2 7 ]` に分割できます。 +3. `inorder` の分割結果から、左部分木と右部分木のノード数はそれぞれ 1 と 3 であることがわかり、したがって `preorder` を `[ 3 | 9 | 2 1 7 ]` に分割できます。 -![前順走査と中順走査での部分木の分割](build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png) +![前順走査と中順走査で部分木を分割する](build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png) -### 変数に基づく部分木範囲の記述 +### 変数を用いて部分木区間を記述する -上記の分割方法に基づいて、**`preorder` と `inorder` シーケンスにおけるルート、左の部分木、右の部分木のインデックス範囲を取得しました**。これらのインデックス範囲を記述するために、いくつかのポインタ変数を使用します。 +以上の分割方法により、**根ノード、左部分木、右部分木が `preorder` と `inorder` の中で占めるインデックス区間**が得られました。これらのインデックス区間を表すために、いくつかのポインタ変数を導入します。 -- 現在の木のルートノードの `preorder` シーケンスでのインデックスを $i$ とします。 -- 現在の木のルートノードの `inorder` シーケンスでのインデックスを $m$ とします。 -- 現在の木の `inorder` シーケンスでのインデックス範囲を $[l, r]$ とします。 +- 現在の木の根ノードが `preorder` に現れるインデックスを $i$ とします。 +- 現在の木の根ノードが `inorder` に現れるインデックスを $m$ とします。 +- 現在の木が `inorder` において占めるインデックス区間を $[l, r]$ とします。 -以下の表に示すように、これらの変数は `preorder` シーケンスでのルートノードのインデックスと `inorder` シーケンスでの部分木のインデックス範囲を表します。 +次の表のように、これらの変数を用いれば根ノードの `preorder` におけるインデックスと、部分木の `inorder` におけるインデックス区間を表せます。 -

  前順走査と中順走査でのルートノードと部分木のインデックス

+

  根ノードと部分木の前順走査・中順走査におけるインデックス

-| | `preorder` でのルートノードインデックス | `inorder` での部分木インデックス範囲 | -| ------------- | ------------------------------------- | ----------------------------------- | -| 現在の木 | $i$ | $[l, r]$ | -| 左の部分木 | $i + 1$ | $[l, m-1]$ | -| 右の部分木 | $i + 1 + (m - l)$ | $[m+1, r]$ | +| | 根ノードの `preorder` におけるインデックス | 部分木の `inorder` におけるインデックス区間 | +| ------ | ---------------------------- | ----------------------------- | +| 現在の木 | $i$ | $[l, r]$ | +| 左部分木 | $i + 1$ | $[l, m-1]$ | +| 右部分木 | $i + 1 + (m - l)$ | $[m+1, r]$ | -右の部分木のルートインデックスの $(m-l)$ は「左の部分木のノード数」を表すことに注意してください。より明確な理解のために、以下の図を参照することが役立つ場合があります。 +右部分木の根ノードのインデックスにある $(m-l)$ は「左部分木のノード数」を意味します。下図と合わせて理解することを勧めます。 -![ルートノードと左右の部分木のインデックス](build_binary_tree_problem.assets/build_tree_division_pointers.png) +![根ノードと左右部分木のインデックス区間の表し方](build_binary_tree_problem.assets/build_tree_division_pointers.png) -### コード実装 +### コードの実装 -$m$ の問い合わせの効率を向上させるために、ハッシュテーブル `hmap` を使用して `inorder` シーケンスの要素からそのインデックスへのマッピングを格納します: +$m$ の検索効率を高めるために、ハッシュテーブル `hmap` を用いて配列 `inorder` の要素からインデックスへの対応を保存します。 ```src [file]{build_tree}-[class]{}-[func]{build_tree} ``` -以下の図は、二分木を構築する再帰過程を示しています。各ノードは再帰の「下降」段階で作成され、各エッジ(参照)は「上昇」段階で形成されます。 +下図は二分木を構築する再帰過程を示しています。各ノードは下向きに「再帰していく」過程で生成され、各辺(参照)は上向きに「戻る」過程で張られます。 === "<1>" - ![二分木構築の再帰過程](build_binary_tree_problem.assets/built_tree_step1.png) + ![二分木を構築する再帰過程](build_binary_tree_problem.assets/built_tree_step1.png) === "<2>" ![built_tree_step2](build_binary_tree_problem.assets/built_tree_step2.png) @@ -90,10 +90,10 @@ $m$ の問い合わせの効率を向上させるために、ハッシュテー === "<9>" ![built_tree_step9](build_binary_tree_problem.assets/built_tree_step9.png) -各再帰関数の `preorder` と `inorder` シーケンスの分割は以下の図に示されています。 +各再帰関数における前順走査 `preorder` と中順走査 `inorder` の分割結果を下図に示します。 -![各再帰関数での分割](build_binary_tree_problem.assets/built_tree_overall.png) +![各再帰関数での分割結果](build_binary_tree_problem.assets/built_tree_overall.png) -二分木が $n$ 個のノードを持つと仮定すると、各ノードの初期化(再帰関数 `dfs()` の呼び出し)には $O(1)$ 時間がかかります。**したがって、全体の時間計算量は $O(n)$ です**。 +木のノード数を $n$ とすると、各ノードの初期化(再帰関数 `dfs()` の 1 回の実行)には $O(1)$ 時間かかります。**したがって、全体の時間計算量は $O(n)$** です。 -ハッシュテーブルは `inorder` 要素からそのインデックスへのマッピングを格納するため、$O(n)$ スペースが必要です。最悪の場合、二分木が連結リストに退化すると、再帰の深さは $n$ に達し、$O(n)$ のスタックスペースを消費する可能性があります。**したがって、全体の空間計算量は $O(n)$ です**。 +ハッシュテーブルには `inorder` の要素からインデックスへの対応を保存するため、空間計算量は $O(n)$ です。最悪の場合、すなわち二分木が連結リストに退化すると、再帰の深さは $n$ に達し、$O(n)$ のスタックフレーム空間を使用します。**したがって、全体の空間計算量は $O(n)$** です。 diff --git a/ja/docs/chapter_divide_and_conquer/divide_and_conquer.md b/ja/docs/chapter_divide_and_conquer/divide_and_conquer.md index a525b4e08..6416e9fb6 100644 --- a/ja/docs/chapter_divide_and_conquer/divide_and_conquer.md +++ b/ja/docs/chapter_divide_and_conquer/divide_and_conquer.md @@ -1,40 +1,40 @@ -# 分割統治アルゴリズム +# 分割統治法 -分割統治は重要で人気のあるアルゴリズム戦略です。名前が示すように、アルゴリズムは通常再帰的に実装され、「分割」と「統治」の2つのステップから構成されます。 +分割統治法(divide and conquer)は、問題を分けて統べるという意味であり、非常に重要で一般的なアルゴリズム戦略です。分割統治法は通常、再帰に基づいて実装され、「分」と「治」の 2 つのステップから構成されます。 -1. **分割(分割段階)**:元の問題を再帰的に2つ以上の小さな部分問題に分解し、最小の部分問題に到達するまで続けます。 -2. **統治(マージ段階)**:解決方法が既知の最小の部分問題から開始し、部分問題の解をボトムアップ方式でマージして元の問題の解を構築します。 +1. **分(分割段階)**:元の問題を 2 つ以上の部分問題へ再帰的に分解し、最小の部分問題に到達した時点で停止します。 +2. **治(統合段階)**:解が既知である最小の部分問題から始めて、部分問題の解を下から上へ統合し、元の問題の解を構築します。 以下の図に示すように、「マージソート」は分割統治戦略の典型的な応用の一つです。 -1. **分割**:元の配列(元の問題)を再帰的に2つの副配列(部分問題)に分割し、副配列が1つの要素のみになるまで(最小の部分問題)続けます。 -2. **統治**:順序付けられた副配列(部分問題の解)をボトムアップでマージして、順序付けられた元の配列(元の問題の解)を取得します。 +1. **分**:元の配列(元の問題)を 2 つの部分配列(部分問題)へ再帰的に分割し、部分配列に要素が 1 つだけ残るまで続けます。 +2. **治**:整列済みの部分配列(部分問題の解)を下から上へ統合し、整列済みの元の配列(元の問題の解)を得ます。 ![マージソートの分割統治戦略](divide_and_conquer.assets/divide_and_conquer_merge_sort.png) -## 分割統治問題を特定する方法 +## 分割統治法の問題を見極めるには -問題が分割統治解決に適しているかどうかは、通常以下の基準に基づいて決定できます。 +ある問題が分割統治法で解くのに適しているかどうかは、通常、次の判断基準を参考にできます。 -1. **問題をより小さなものに分解できる**:元の問題をより小さく類似した部分問題に分割でき、そのような過程を同じ方法で再帰的に実行できます。 -2. **部分問題は独立している**:部分問題間に重複がなく、独立しており、個別に解決できます。 -3. **部分問題の解をマージできる**:元の問題の解は、部分問題の解を組み合わせることで導出されます。 +1. **問題は分解できる**:元の問題は、より小さく類似した部分問題に分解でき、同じ方法で再帰的に分割できます。 +2. **部分問題は独立している**:部分問題同士に重複がなく、相互依存もないため、独立して解決できます。 +3. **部分問題の解は統合できる**:元の問題の解は、部分問題の解を統合することで得られます。 -明らかに、マージソートはこれら3つの基準を満たしています。 +明らかに、マージソートは以上の 3 つの判断基準を満たしています。 -1. **問題をより小さなものに分解できる**:配列(元の問題)を再帰的に2つの副配列(部分問題)に分割します。 -2. **部分問題は独立している**:各副配列は独立してソートできます(部分問題は独立して解決できます)。 -3. **部分問題の解をマージできる**:2つの順序付けられた副配列(部分問題の解)を1つの順序付けられた配列(元の問題の解)にマージできます。 +1. **問題は分解できる**:配列(元の問題)を 2 つの部分配列(部分問題)へ再帰的に分割します。 +2. **部分問題は独立している**:各部分配列は独立にソートできます(部分問題は独立に解けます)。 +3. **部分問題の解は統合できる**:2 つの整列済み部分配列(部分問題の解)は、1 つの整列済み配列(元の問題の解)に統合できます。 -## 分割統治による効率の向上 +## 分割統治法で効率を高める -**分割統治戦略はアルゴリズム問題を効果的に解決するだけでなく、しばしば効率を向上させます**。ソートアルゴリズムでは、クイックソート、マージソート、ヒープソートは、分割統治戦略を適用しているため、選択ソート、バブルソート、挿入ソートよりも高速です。 +**分割統治法はアルゴリズムの問題を効果的に解けるだけでなく、多くの場合アルゴリズムの効率も高められます**。ソートアルゴリズムでは、クイックソート、マージソート、ヒープソートが選択ソート、バブルソート、挿入ソートより高速ですが、これは分割統治戦略を適用しているためです。 -私たちの心には疑問があるかもしれません:**なぜ分割統治はアルゴリズムの効率を向上させることができ、その根本的な論理は何ですか?** つまり、問題を部分問題に分解し、それらを解決し、それらの解を組み合わせて元の問題に対処することが、元の問題を直接解決するよりも効率的である理由は何ですか?この質問は2つの側面から分析できます:操作数と並列計算。 +ここで次の疑問が生じます。**なぜ分割統治法はアルゴリズム効率を高められるのでしょうか。その根本的な仕組みは何でしょうか**?言い換えると、大きな問題を複数の部分問題に分解し、部分問題を解き、それらの解を統合して元の問題の解にするという手順は、なぜ元の問題を直接解くより効率的なのでしょうか。この問題は、操作回数と並列計算の 2 つの観点から議論できます。 -### 操作数の最適化 +### 操作回数の最適化 -「バブルソート」を例にとると、長さ $n$ の配列を処理するのに $O(n^2)$ 時間が必要です。以下の図に示すように、配列を中点から2つの副配列に分割するとします。そのような分割には $O(n)$ 時間が必要です。各副配列のソートには $O((n / 2)^2)$ 時間が必要です。そして2つの副配列のマージには $O(n)$ 時間が必要です。したがって、全体の時間計算量は: +「バブルソート」を例に取ると、長さ $n$ の配列を処理するのに $O(n^2)$ の時間がかかります。以下の図のように、配列を中央で 2 つの部分配列に分けると仮定すると、分割には $O(n)$ の時間、各部分配列のソートには $O((n / 2)^2)$ の時間、2 つの部分配列の統合には $O(n)$ の時間が必要で、全体の時間計算量は次のようになります: $$ O(n + (\frac{n}{2})^2 \times 2 + n) = O(\frac{n^2}{2} + 2n) @@ -42,7 +42,7 @@ $$ ![配列分割前後のバブルソート](divide_and_conquer.assets/divide_and_conquer_bubble_sort.png) -以下の不等式を計算してみましょう。左側は分割前の総操作数を表し、右側は分割後の総操作数をそれぞれ表します: +次に、以下の不等式を計算します。左辺と右辺はそれぞれ、分割前と分割後の操作総数です: $$ \begin{aligned} @@ -52,40 +52,40 @@ n(n - 4) & > 0 \end{aligned} $$ -**これは $n > 4$ の場合、分割後の操作数が少なく、より良いパフォーマンスにつながることを意味します**。分割後の時間計算量は依然として二次 $O(n^2)$ ですが、計算量の定数係数が減少していることに注意してください。 +**これは、$n > 4$ のときに分割後の操作回数の方が少なくなり、ソート効率が高くなることを意味します**。ただし、分割後の時間計算量は依然として 2 次の $O(n^2)$ であり、計算量の定数項が小さくなっただけです。 -さらに進むことができます。**副配列をその中点からさらに2つの副配列に分割し続けて、副配列が1つの要素のみになるまで続けたらどうでしょうか?** このアイデアは実際には「マージソート」で、時間計算量は $O(n \log n)$ です。 +さらに考えると、**部分配列を中央からさらに 2 つの部分配列へと分割し続け**、部分配列に要素が 1 つだけ残るまで分割を止めないとしたらどうでしょうか。この考え方がまさに「マージソート」であり、時間計算量は $O(n \log n)$ です。 -少し違うことを試してみましょう。**2つではなく、より多くの分割に分割したらどうでしょうか?** 例えば、元の配列を $k$ 個の副配列に均等に分割しますか?このアプローチは「バケットソート」と非常に似ており、大量のデータのソートに非常に適しています。理論的には、時間計算量は $O(n + k)$ に達することができます。 +さらに、**分割点をいくつか増やして**、元の配列を平均的に $k$ 個の部分配列に分けるとしたらどうでしょうか。この状況は「バケットソート」と非常によく似ており、大量データのソートに非常に適しています。理論上の時間計算量は $O(n + k)$ に達します。 -### 並列計算による最適化 +### 並列計算の最適化 -分割統治によって生成される部分問題は互いに独立していることが分かっています。**これは、それらを並列で解決できることを意味します。** その結果、分割統治はアルゴリズムの時間計算量を減らすだけでなく、**現代のオペレーティングシステムによる並列最適化も促進します。** +分割統治法で生成される部分問題は互いに独立しているため、**通常は並列に解くことができます**。つまり、分割統治法はアルゴリズムの時間計算量を下げられるだけでなく、**オペレーティングシステムの並列最適化にも有利です**。 -並列最適化は、複数のコアやプロセッサを持つ環境で特に効果的です。システムが複数の部分問題を同時に処理できるため、計算リソースを完全に活用し、全体的な実行時間が大幅に短縮されます。 +並列最適化は、マルチコアまたはマルチプロセッサ環境で特に有効です。システムが複数の部分問題を同時に処理でき、計算資源をより十分に活用できるため、全体の実行時間を大幅に短縮できます。 -例えば、以下の図に示す「バケットソート」では、大量のデータを様々なバケットに均等に分解します。各バケットのソート作業は、利用可能な計算ユニットに割り当てることができます。すべての作業が完了すると、すべてのソートされたバケットがマージされて最終結果が生成されます。 +たとえば、以下の図に示す「バケットソート」では、大量のデータを各バケットに均等に割り当てることで、すべてのバケットのソート処理を各計算ユニットに分散し、完了後に結果を統合できます。 ![バケットソートの並列計算](divide_and_conquer.assets/divide_and_conquer_parallel_computing.png) -## 分割統治の一般的な応用 +## 分割統治法の代表的な応用 -分割統治は多くの古典的なアルゴリズム問題を解決するために使用できます。 +一方では、分割統治法は多くの古典的なアルゴリズム問題を解くのに使えます。 -- **最近点対の発見**:このアルゴリズムは点の集合を2つの半分に分割することで動作します。そして各半分で再帰的に最近点対を見つけます。最後に、2つの半分にまたがるペアを考慮して、全体の最近点対を見つけます。 -- **大整数の乗算**:一つのアルゴリズムはKaratsubaと呼ばれます。大整数の乗算をいくつかの小さな整数の乗算と加算に分解します。 -- **行列の乗算**:一例はStrassenアルゴリズムです。大きな行列の乗算を複数の小さな行列の乗算と加算に分解します。 -- **ハノイの塔問題**:ハノイの塔問題は再帰的に解決でき、分割統治戦略の典型的な応用です。 -- **転倒対の解決**:シーケンスで、前の数が後の数より大きい場合、これら2つの数は転倒対を構成します。転倒対問題の解決は、マージソートの助けを借りて、分割統治のアイデアを利用できます。 +- **最近点対探索**:このアルゴリズムは、まず点集合を 2 つに分け、それぞれの部分における最近点対を求め、最後に 2 つの部分をまたぐ最近点対を求めます。 +- **大整数乗算**:たとえば Karatsuba 法では、大整数の乗算を、より小さな整数どうしのいくつかの乗算と加算に分解します。 +- **行列乗算**:たとえば Strassen 法では、大きな行列の乗算を、複数の小さな行列の乗算と加算に分解します。 +- **ハノイの塔問題**:ハノイの塔問題は再帰によって解くことができ、これは典型的な分割統治戦略の応用です。 +- **反転対の計算**:ある数列で前の数が後ろの数より大きい場合、その 2 つの数は反転対を構成します。反転対の問題は、分割統治の考え方を利用し、マージソートを用いて解けます。 -分割統治はアルゴリズムとデータ構造の設計にも広く応用されています。 +他方で、分割統治法はアルゴリズムとデータ構造の設計にも非常に広く応用されています。 -- **二分探索**:二分探索は、ソート済み配列を中点インデックスから2つの半分に分割します。そして、ターゲット値と中間要素値の比較結果に基づいて、一方の半分が破棄されます。同じプロセスで残りの半分で検索が続行され、ターゲットが見つかるか残りの要素がなくなるまで続きます。 -- **マージソート**:この節の冒頭ですでに紹介したため、さらなる詳述は不要です。 -- **クイックソート**:クイックソートはピボット値を選択して配列を2つの副配列に分割し、一方はピボットより小さい要素、もう一方はピボットより大きい要素を持ちます。このプロセスは、これら2つの副配列のそれぞれに対して、1つの要素のみを保持するまで続きます。 -- **バケットソート**:バケットソートの基本的なアイデアは、データを複数のバケットに分散させることです。各バケット内の要素をソートした後、バケットから順序よく要素を取得して順序付けられた配列を取得します。 -- **木**:例えば、二分探索木、AVL木、赤黒木、B木、B+木など。その操作(検索、挿入、削除)はすべて分割統治戦略の応用と見なすことができます。 -- **ヒープ**:ヒープは特別なタイプの完全二分木です。その様々な操作(挿入、削除、ヒープ化)は、実際に分割統治のアイデアを含意しています。 -- **ハッシュテーブル**:ハッシュテーブルは直接分割統治を適用しませんが、一部のハッシュ衝突解決ソリューションは間接的にこの戦略を適用します。例えば、チェイン法の長いリストは、クエリ効率を向上させるために赤黒木に変換される場合があります。 +- **二分探索**:二分探索では、整列済み配列を中央のインデックスで 2 つに分け、目標値と中央要素の比較結果に基づいてどちらの半区間を除外するかを決め、残った区間で同じ二分操作を行います。 +- **マージソート**:本節の冒頭で紹介したため、ここでは繰り返しません。 +- **クイックソート**:クイックソートは基準値を 1 つ選び、配列を、基準値より小さい要素の部分配列と、基準値より大きい要素の部分配列に分け、その後それぞれに対して同じ分割操作を行い、部分配列に要素が 1 つだけ残るまで続けます。 +- **バケットソート**:バケットソートの基本的な考え方は、データを複数のバケットに分散し、各バケット内の要素をソートしたうえで、各バケットの要素を順に取り出して整列済み配列を得ることです。 +- **木構造**:たとえば二分探索木、AVL 木、赤黒木、B 木、B+ 木などでは、探索・挿入・削除などの操作をいずれも分割統治戦略の応用とみなせます。 +- **ヒープ**:ヒープは特殊な完全二分木であり、挿入、削除、ヒープ化などの各種操作には、実際には分割統治の考え方が含まれています。 +- **ハッシュテーブル**:ハッシュテーブル自体は分割統治を直接適用しているわけではありませんが、いくつかのハッシュ衝突解決法では間接的に分割統治戦略が使われています。たとえば、連鎖アドレス法における長い連結リストは、検索効率を高めるために赤黒木へ変換されます。 -**分割統治は巧妙に浸透するアルゴリズムアイデア**であり、様々なアルゴリズムとデータ構造に組み込まれていることが分かります。 +このように、**分割統治法は「静かに物を潤す」ようなアルゴリズム思想**であり、さまざまなアルゴリズムやデータ構造の中に潜んでいます。 diff --git a/ja/docs/chapter_divide_and_conquer/hanota_problem.md b/ja/docs/chapter_divide_and_conquer/hanota_problem.md index 2a8cb8e56..773433a29 100644 --- a/ja/docs/chapter_divide_and_conquer/hanota_problem.md +++ b/ja/docs/chapter_divide_and_conquer/hanota_problem.md @@ -1,37 +1,37 @@ -# ハノイの塔問題 +# ハノイの塔の問題 -マージソートと二分木構築の両方で、元の問題を2つの部分問題に分解し、それぞれが元の問題のサイズの半分でした。しかし、ハノイの塔では、異なる分解戦略を採用します。 +マージソートや二分木の構築では、いずれも元の問題を元問題の半分の規模をもつ 2 つの部分問題に分解していました。しかし、ハノイの塔の問題では、異なる分解戦略を採用します。 !!! question - 3つの柱があり、それぞれ `A`、`B`、`C` と表記されます。最初、柱 `A` には $n$ 枚の円盤があり、上から下に向かって昇順のサイズで配置されています。私たちのタスクは、これらの $n$ 枚の円盤を柱 `C` に移動し、元の順序を維持することです(以下の図に示すように)。移動中には以下のルールが適用されます: + 3 本の柱があり、それぞれを `A`、`B`、`C` とします。初期状態では、柱 `A` に $n$ 枚の円盤が通されており、上から下へ小さい順に並んでいます。私たちの課題は、この $n$ 枚の円盤を柱 `C` に移し、元の順序を保つことです(以下の図のとおり)。円盤を移動する際には、次のルールに従う必要があります。 + + 1. 円盤は 1 本の柱の頂上から取り出し、別の柱の頂上に置くことしかできません。 + 2. 1 回に移動できる円盤は 1 枚だけです。 + 3. 小さい円盤は常に大きい円盤の上になければなりません。 - 1. 円盤は柱の上部からのみ取り除くことができ、別の柱の上部に置く必要があります。 - 2. 一度に移動できるのは1枚の円盤のみです。 - 3. 小さい円盤は常に大きい円盤の上にある必要があります。 +![ハノイの塔の問題の例](hanota_problem.assets/hanota_example.png) -![ハノイの塔の例](hanota_problem.assets/hanota_example.png) - -**サイズ $i$ のハノイの塔問題を $f(i)$ と表記します**。例えば、$f(3)$ は3枚の円盤を柱 `A` から柱 `C` に移動することを表します。 +**規模が $i$ のハノイの塔の問題を $f(i)$ と表します** 。たとえば $f(3)$ は、$3$ 枚の円盤を `A` から `C` へ移動するハノイの塔の問題を表します。 ### 基本ケースを考える -以下の図に示すように、問題 $f(1)$(円盤が1枚のみ)については、`A` から `C` に直接移動できます。 +以下の図に示すように、問題 $f(1)$ 、すなわち円盤が 1 枚だけの場合は、それを `A` から `C` へ直接移動すれば済みます。 === "<1>" - ![サイズ1の問題の解](hanota_problem.assets/hanota_f1_step1.png) + ![規模 1 の問題の解](hanota_problem.assets/hanota_f1_step1.png) === "<2>" ![hanota_f1_step2](hanota_problem.assets/hanota_f1_step2.png) -$f(2)$(円盤が2枚)については、**柱 `B` の助けを借りて小さい円盤を大きい円盤の上に保つ**必要があります。以下の図に示すように: +以下の図に示すように、問題 $f(2)$ 、すなわち円盤が 2 枚ある場合は、**小さい円盤が常に大きい円盤の上にある条件を満たすため、`B` を借りて移動を行う必要があります**。 -1. まず、小さい円盤を `A` から `B` に移動します。 -2. 次に、大きい円盤を `A` から `C` に移動します。 -3. 最後に、小さい円盤を `B` から `C` に移動します。 +1. まず上の小さい円盤を `A` から `B` へ移します。 +2. 次に大きい円盤を `A` から `C` へ移します。 +3. 最後に小さい円盤を `B` から `C` へ移します。 === "<1>" - ![サイズ2の問題の解](hanota_problem.assets/hanota_f2_step1.png) + ![規模 2 の問題の解](hanota_problem.assets/hanota_f2_step1.png) === "<2>" ![hanota_f2_step2](hanota_problem.assets/hanota_f2_step2.png) @@ -42,20 +42,20 @@ $f(2)$(円盤が2枚)については、**柱 `B` の助けを借りて小さ === "<4>" ![hanota_f2_step4](hanota_problem.assets/hanota_f2_step4.png) -$f(2)$ を解決する過程は次のように要約できます:**`B` の助けを借りて2枚の円盤を `A` から `C` に移動する**。ここで、`C` をターゲット柱、`B` をバッファ柱と呼びます。 +問題 $f(2)$ を解く過程は、**2 枚の円盤を `B` を介して `A` から `C` へ移す**と要約できます。このとき、`C` を目標の柱、`B` を補助の柱と呼びます。 -### 部分問題の分解 +### 部分問題への分解 -問題 $f(3)$(つまり、円盤が3枚の場合)については、状況がやや複雑になります。 +問題 $f(3)$ 、すなわち円盤が 3 枚ある場合になると、状況はやや複雑になります。 -すでに $f(1)$ と $f(2)$ の解が分かっているので、分割統治の観点を採用し、**`A` の上の2枚の円盤を1つの単位として扱い**、以下の図に示すステップを実行できます。これにより、3枚の円盤を `A` から `C` に正常に移動できます。 +$f(1)$ と $f(2)$ の解が既知なので、分割統治の観点から、**`A` の上部にある 2 枚の円盤をひとまとまりとみなして**、次の図の手順を実行できます。こうして 3 枚の円盤を `A` から `C` へ順調に移動できます。 -1. `B` をターゲット柱、`C` をバッファ柱として、2枚の円盤を `A` から `B` に移動します。 -2. 残りの円盤を `A` から直接 `C` に移動します。 -3. `C` をターゲット柱、`A` をバッファ柱として、2枚の円盤を `B` から `C` に移動します。 +1. `B` を目標の柱、`C` を補助の柱として、2 枚の円盤を `A` から `B` へ移します。 +2. `A` に残った 1 枚の円盤を `A` から `C` へ直接移動します。 +3. `C` を目標の柱、`A` を補助の柱として、2 枚の円盤を `B` から `C` へ移します。 === "<1>" - ![サイズ3の問題の解](hanota_problem.assets/hanota_f3_step1.png) + ![規模 3 の問題の解](hanota_problem.assets/hanota_f3_step1.png) === "<2>" ![hanota_f3_step2](hanota_problem.assets/hanota_f3_step2.png) @@ -66,32 +66,32 @@ $f(2)$ を解決する過程は次のように要約できます:**`B` の助 === "<4>" ![hanota_f3_step4](hanota_problem.assets/hanota_f3_step4.png) -本質的に、**$f(3)$ を2つの $f(2)$ 部分問題と1つの $f(1)$ 部分問題に分解します**。これら3つの部分問題を順次解決することで、元の問題が解決され、部分問題が独立しており、それらの解をマージできることを示しています。 +本質的には、**問題 $f(3)$ を 2 つの部分問題 $f(2)$ と 1 つの部分問題 $f(1)$ に分けています** 。この 3 つの部分問題を順に解けば、元の問題も解決されます。これは、部分問題が独立しており、解を組み合わせられることを示しています。 -ここから、以下の図に示すハノイの塔の分割統治戦略を要約できます。元の問題 $f(n)$ を2つの部分問題 $f(n-1)$ と1つの部分問題 $f(1)$ に分割し、以下の順序でこれら3つの部分問題を解決します: +ここまでで、次の図に示すハノイの塔の問題を解く分割統治戦略をまとめられます。元の問題 $f(n)$ を 2 つの部分問題 $f(n-1)$ と 1 つの部分問題 $f(1)$ に分け、次の順序でこの 3 つの部分問題を解きます。 -1. `C` をバッファとして使用し、$n-1$ 枚の円盤を `A` から `B` に移動します。 -2. 残りの円盤を `A` から直接 `C` に移動します。 -3. `A` をバッファとして使用し、$n-1$ 枚の円盤を `B` から `C` に移動します。 +1. $n-1$ 枚の円盤を `C` を介して `A` から `B` へ移します。 +2. 残り $1$ 枚の円盤を `A` から `C` へ直接移します。 +3. $n-1$ 枚の円盤を `A` を介して `B` から `C` へ移します。 -各 $f(n-1)$ 部分問題について、**同じ再帰分割を適用でき**、最小の部分問題 $f(1)$ に到達するまで続けます。$f(1)$ は単一の移動のみが必要であることがすでに分かっているため、解決するのは簡単です。 +この 2 つの部分問題 $f(n-1)$ は、**同じ方法で再帰的に分割できます**。最小の部分問題 $f(1)$ に到達するまでこれを続けます。一方、$f(1)$ の解は既知であり、1 回の移動操作だけで済みます。 -![ハノイの塔を解決するための分割統治戦略](hanota_problem.assets/hanota_divide_and_conquer.png) +![ハノイの塔の問題を解く分割統治戦略](hanota_problem.assets/hanota_divide_and_conquer.png) -### コード実装 +### コードの実装 -コードでは、再帰関数 `dfs(i, src, buf, tar)` を定義します。これは柱 `src` から上の $i$ 枚の円盤を柱 `tar` に移動し、柱 `buf` をバッファとして使用します: +コードでは、再帰関数 `dfs(i, src, buf, tar)` を定義します。その役割は、柱 `src` の上部にある $i$ 枚の円盤を、補助の柱 `buf` を使って目標の柱 `tar` へ移動することです: ```src [file]{hanota}-[class]{}-[func]{solve_hanota} ``` -以下の図に示すように、ハノイの塔問題は高さ $n$ の再帰木として視覚化できます。各ノードは部分問題を表し、`dfs()` の呼び出しに対応します。**したがって、時間計算量は $O(2^n)$、空間計算量は $O(n)$ です。** +以下の図に示すように、ハノイの塔の問題は高さ $n$ の再帰木を形成し、各ノードは 1 つの部分問題、すなわち 1 つ起動された `dfs()` 関数に対応します。**したがって時間計算量は $O(2^n)$ 、空間計算量は $O(n)$** です。 -![ハノイの塔の再帰木](hanota_problem.assets/hanota_recursive_tree.png) +![ハノイの塔の問題の再帰木](hanota_problem.assets/hanota_recursive_tree.png) !!! quote - ハノイの塔は古代の伝説に由来します。古代インドの寺院で、僧侶たちは3本の高いダイヤモンドの柱と、異なるサイズの $64$ 枚の金の円盤を持っていました。彼らは、最後の円盤が正しく置かれたとき、世界が終わると信じていました。 + ハノイの塔の問題は古い伝説に由来します。古代インドのある寺院で、僧侶たちは 3 本の高いダイヤモンドの柱と、$64$ 枚の大きさの異なる金の円盤を持っていました。僧侶たちは絶えず円盤を動かし、最後の 1 枚が正しく置かれた瞬間に世界が終わると信じていました。 - しかし、僧侶たちが1秒に1枚の円盤を移動したとしても、約 $2^{64} \approx 1.84×10^{19}$ —約5850億年—かかり、宇宙の年齢の現在の推定をはるかに超えています。したがって、この伝説が真実であれば、世界の終わりについて心配する必要はおそらくないでしょう。 + しかし、たとえ僧侶たちが 1 秒に 1 回移動するとしても、合計でおよそ $2^{64} \approx 1.84×10^{19}$ 秒、約 $5850$ 億年が必要で、現在推定されている宇宙の年齢をはるかに上回ります。したがって、この伝説が本当だったとしても、世界の終わりを心配する必要はなさそうです。 diff --git a/ja/docs/chapter_divide_and_conquer/index.md b/ja/docs/chapter_divide_and_conquer/index.md index 60f7dcf60..67f8d40fb 100644 --- a/ja/docs/chapter_divide_and_conquer/index.md +++ b/ja/docs/chapter_divide_and_conquer/index.md @@ -4,6 +4,6 @@ !!! abstract - 困難な問題は層を重ねて分解され、各分解によってより単純になります。 - - 分割統治は深い真理を明らかにします:単純さから始めれば、複雑さは解決される。 + 難題は段階的に分解され、そのたびにより単純になっていく。 + + 分割統治は一つの重要な事実を示している。単純なことから始めれば、すべてはもはや複雑ではない。 diff --git a/ja/docs/chapter_divide_and_conquer/summary.md b/ja/docs/chapter_divide_and_conquer/summary.md index 60ef09e15..e378f5c5a 100644 --- a/ja/docs/chapter_divide_and_conquer/summary.md +++ b/ja/docs/chapter_divide_and_conquer/summary.md @@ -1,11 +1,13 @@ # まとめ -- 分割統治は一般的なアルゴリズム設計戦略で、分割(分割)と統治(マージ)の2つの段階から構成され、一般的に再帰を使用して実装されます。 -- 問題が分割統治アプローチに適しているかどうかを判断するために、問題が分解可能かどうか、部分問題が独立しているかどうか、部分問題をマージできるかどうかを確認します。 -- マージソートは分割統治戦略の典型的な例です。配列を再帰的に2つの等しい長さの副配列に分割し、1つの要素のみが残るまで続け、次にこれらの副配列を層ごとにマージしてソートを完了します。 -- 分割統治戦略の導入は、しばしばアルゴリズムの効率を向上させます。一方では操作数を減らし、他方では分割後のシステムの並列最適化を促進します。 -- 分割統治は多数のアルゴリズム問題に適用でき、データ構造とアルゴリズム設計で広く使用され、多くのシナリオに現れます。 -- 総当たり検索と比較して、適応検索はより効率的です。時間計算量が $O(\log n)$ の検索アルゴリズムは、通常分割統治戦略に基づいています。 -- 二分探索は分割統治戦略のもう一つの古典的な応用です。部分問題の解のマージを含まず、再帰的な分割統治アプローチで実装できます。 -- 二分木構築問題では、木の構築(元の問題)を左の部分木と右の部分木の構築(部分問題)に分割できます。これは前順走査と中順走査のインデックス範囲を分割することで実現できます。 -- ハノイの塔問題では、サイズ $n$ の問題をサイズ $n-1$ の2つの部分問題とサイズ $1$ の1つの部分問題に分解できます。これら3つの部分問題を順次解決することで、元の問題が解決されます。 +### 要点の振り返り + +- 分割統治法は一般的なアルゴリズム設計戦略であり、分(分割)と治(統合)の 2 つの段階からなり、通常は再帰に基づいて実装されます。 +- それが分割統治法の問題かどうかを判断する基準には、問題を分解できるか、部分問題が独立しているか、部分問題を統合できるかが含まれます。 +- マージソートは分割統治法の典型的な応用であり、配列を再帰的に同じ長さの 2 つの部分配列に分割し、要素が 1 つだけになるまで続け、その後で各層を順に統合してソートを完了します。 +- 分割統治法を導入すると、多くの場合アルゴリズムの効率を高められます。一方では操作回数が減り、他方では分割後にシステムの並列最適化を行いやすくなります。 +- 分割統治法は多くのアルゴリズム問題を解決できるだけでなく、データ構造やアルゴリズム設計にも広く応用されており、至る所でその姿を見ることができます。 +- 総当たり探索と比べて、適応的な探索のほうが効率的です。時間計算量が $O(\log n)$ の探索アルゴリズムは、通常は分割統治法に基づいて実装されます。 +- 二分探索は分割統治法のもう 1 つの典型的な応用であり、部分問題の解を統合する手順を含みません。再帰的な分割統治によって二分探索を実現できます。 +- 二分木を構築する問題では、木の構築(元の問題)を左部分木と右部分木の構築(部分問題)に分けられます。これは、先行順序走査と中間順序走査のインデックス区間を分割することで実現できます。 +- ハノイの塔の問題では、規模 $n$ の問題を、規模 $n-1$ の 2 つの部分問題と規模 $1$ の 1 つの部分問題に分けられます。これら 3 つの部分問題を順に解くと、元の問題も解決されます。 diff --git a/ja/docs/chapter_dynamic_programming/dp_problem_features.md b/ja/docs/chapter_dynamic_programming/dp_problem_features.md index e94c279ad..0768fc7d6 100644 --- a/ja/docs/chapter_dynamic_programming/dp_problem_features.md +++ b/ja/docs/chapter_dynamic_programming/dp_problem_features.md @@ -1,79 +1,79 @@ -# 動的プログラミング問題の特徴 +# 動的計画法の問題特性 -前のセクションでは、動的プログラミングが問題を部分問題に分解することで元の問題を解決する方法を学びました。実際、部分問題の分解は一般的なアルゴリズムアプローチであり、分割統治法、動的プログラミング、バックトラッキングでは異なる重点があります。 +前節では、動的計画法が部分問題への分解によってどのように元の問題を解くのかを学びました。実際、部分問題への分解は汎用的なアルゴリズムの考え方であり、分割統治法、動的計画法、バックトラッキングでは重視点が異なります。 -- 分割統治法アルゴリズムは元の問題を複数の独立した部分問題に再帰的に分割し、最小の部分問題に到達するまで続け、バックトラッキング時に部分問題の解を組み合わせて最終的に元の問題の解を得ます。 -- 動的プログラミングも問題を再帰的に分解しますが、分割統治法アルゴリズムとの主な違いは、動的プログラミングの部分問題が相互依存的であり、分解プロセス中に多くの重複する部分問題が現れることです。 -- バックトラッキングアルゴリズムは試行錯誤によってすべての可能な解を網羅し、枝刈りによって不必要な探索分岐を避けます。元の問題の解は一連の決定ステップから構成され、各決定ステップ前の各部分シーケンスを部分問題として考えることができます。 +- 分割統治法は、元の問題を再帰的に複数の互いに独立した部分問題へ分割し、最小の部分問題に至るまで分解したうえで、バックトラック時に部分問題の解を統合し、最終的に元の問題の解を得ます。 +- 動的計画法も問題を再帰的に分解しますが、分割統治法との主な違いは、動的計画法における部分問題が相互依存しており、分解の過程で多数の重複部分問題が現れることです。 +- バックトラッキング法は、試行と巻き戻しの中ですべての可能な解を列挙し、枝刈りによって不要な探索分岐を避けます。元の問題の解は一連の意思決定ステップから構成されるため、各決定ステップ以前の部分系列を一つの部分問題と見なせます。 -実際、動的プログラミングは最適化問題を解決するためによく使用され、これらは重複する部分問題を含むだけでなく、他に2つの主要な特徴があります:最適部分構造と無記憶性です。 +実際、動的計画法は最適化問題を解くためによく用いられます。これらは重複部分問題を含むだけでなく、さらに二つの大きな特性、すなわち最適部分構造と無後効性を備えています。 ## 最適部分構造 -階段登り問題を少し修正して、最適部分構造の概念を実証するのにより適したものにします。 +階段昇り問題を少し変更し、最適部分構造の概念をより示しやすくします。 -!!! question "階段登りの最小コスト" +!!! question "階段昇りの最小コスト" - 階段があり、一度に1段または2段上ることができ、階段の各段にはその段で支払う必要があるコストを表す非負の整数があります。非負の整数配列 $cost$ が与えられ、$cost[i]$ は $i$ 段目で支払う必要があるコストを表し、$cost[0]$ は地面(開始点)です。頂上に到達するために必要な最小コストは何ですか? + 階段が与えられ、各ステップで $1$ 段または $2$ 段上ることができます。各段には非負整数が貼られており、その段に到達するために支払う必要があるコストを表します。非負整数配列 $cost$ が与えられ、$cost[i]$ は第 $i$ 段で支払うコストを表し、$cost[0]$ は地面(開始地点)です。頂上に到達するために必要な最小コストを求めてください。 -下の図に示すように、1段目、2段目、3段目のコストがそれぞれ $1$、$10$、$1$ の場合、地面から3段目に登る最小コストは $2$ です。 +下図に示すように、第 $1$、$2$、$3$ 段のコストがそれぞれ $1$、$10$、$1$ である場合、地面から第 $3$ 段まで上る最小コストは $2$ です。 -![3段目に登る最小コスト](dp_problem_features.assets/min_cost_cs_example.png) +![第 3 段まで上る最小コスト](dp_problem_features.assets/min_cost_cs_example.png) -$dp[i]$ を $i$ 段目に登る累積コストとします。$i$ 段目は $i-1$ 段目または $i-2$ 段目からのみ来ることができるため、$dp[i]$ は $dp[i-1] + cost[i]$ または $dp[i-2] + cost[i]$ のいずれかしかありえません。コストを最小化するために、2つのうち小さい方を選択すべきです: +$dp[i]$ を第 $i$ 段まで上るのに累積して支払ったコストとします。第 $i$ 段には $i - 1$ 段または $i - 2$ 段からしか到達できないため、$dp[i]$ は $dp[i - 1] + cost[i]$ または $dp[i - 2] + cost[i]$ のいずれかになります。コストをできるだけ小さくするには、この二つのうち小さいほうを選べばよいです。 $$ dp[i] = \min(dp[i-1], dp[i-2]) + cost[i] $$ -これにより最適部分構造の意味がわかります:**元の問題の最適解は部分問題の最適解から構築される**。 +ここから最適部分構造の意味を導けます。**元の問題の最適解は、部分問題の最適解から構築される**ということです。 -この問題は明らかに最適部分構造を持っています:2つの部分問題 $dp[i-1]$ と $dp[i-2]$ の最適解からより良い方を選択し、それを使用して元の問題 $dp[i]$ の最適解を構築します。 +この問題が最適部分構造を持つことは明らかです。二つの部分問題の最適解 $dp[i-1]$ と $dp[i-2]$ からより良いほうを選び、それを用いて元の問題 $dp[i]$ の最適解を構築しています。 -では、前のセクションの階段登り問題は最適部分構造を持っているでしょうか?その目標は解の数を求めることで、これは数え上げ問題のようですが、別の方法で尋ねてみましょう:「解の最大数を求める」。驚くことに、**問題が変わったにもかかわらず、最適部分構造が現れた**ことがわかります:$n$ 段目での解の最大数は、$n-1$ 段目と $n-2$ 段目での解の最大数の和に等しいです。したがって、最適部分構造の解釈は非常に柔軟で、異なる問題では異なる意味を持ちます。 +では、前節の階段昇り問題には最適部分構造があるのでしょうか。その目的は方法数を求めることで、一見すると計数問題です。しかし問い方を変えて「最大の方法数を求める」とすると、意外にも、**問題の変更前後は等価であるにもかかわらず、最適部分構造が現れます**。すなわち、第 $n$ 段の最大方法数は第 $n-1$ 段と第 $n-2$ 段の最大方法数の和に等しいのです。このように、最適部分構造の解釈は比較的柔軟であり、問題によって意味合いが異なります。 -状態遷移方程式と初期状態 $dp[1] = cost[1]$ および $dp[2] = cost[2]$ に従って、動的プログラミングコードを得ることができます: +状態遷移方程式と初期状態 $dp[1] = cost[1]$ および $dp[2] = cost[2]$ に基づいて、次の動的計画法コードが得られます。 ```src [file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp} ``` -下の図は上記コードの動的プログラミングプロセスを示しています。 +下図は上記コードの動的計画法の過程を示しています。 -![階段登りの最小コストの動的プログラミングプロセス](dp_problem_features.assets/min_cost_cs_dp.png) +![階段昇り最小コストの動的計画法の過程](dp_problem_features.assets/min_cost_cs_dp.png) -この問題も空間最適化が可能で、1次元を0に圧縮し、空間計算量を $O(n)$ から $O(1)$ に削減できます: +この問題では空間最適化も可能であり、一次元をゼロ次元まで圧縮することで、空間計算量を $O(n)$ から $O(1)$ に削減できます。 ```src [file]{min_cost_climbing_stairs_dp}-[class]{}-[func]{min_cost_climbing_stairs_dp_comp} ``` -## 無記憶性 +## 無後効性 -無記憶性は動的プログラミングが問題解決に効果的であることを可能にする重要な特徴の1つです。その定義は:**特定の状態が与えられたとき、その将来の発展は現在の状態のみに関連し、過去に経験したすべての状態とは無関係である**。 +無後効性は、動的計画法が問題を効率よく解ける重要な特性の一つであり、その定義は次のとおりです。**ある確定した状態が与えられたとき、その後の発展は現在の状態のみに依存し、過去に経たすべての状態には依存しない**。 -階段登り問題を例に取ると、状態 $i$ が与えられたとき、それは状態 $i+1$ と $i+2$ に発展し、それぞれ1段ジャンプと2段ジャンプに対応します。これら2つの選択をするとき、状態 $i$ より前の状態を考慮する必要はありません。なぜなら、それらは状態 $i$ の将来に影響しないからです。 +階段昇り問題を例にすると、状態 $i$ が与えられたとき、そこから状態 $i+1$ と状態 $i+2$ へ発展し、それぞれ $1$ 段進む場合と $2$ 段進む場合に対応します。この二つの選択を行う際、状態 $i$ より前の状態を考慮する必要はなく、それらは状態 $i$ の将来に影響を与えません。 -しかし、階段登り問題に制約を追加すると、状況が変わります。 +しかし、階段昇り問題に制約を一つ追加すると、状況は変わります。 -!!! question "制約付き階段登り" +!!! question "制約付き階段昇り" - $n$ 段の階段があり、毎回1段または2段上ることができますが、**1段を2回連続でジャンプすることはできません**。頂上に登る方法は何通りありますか? + 全部で $n$ 段ある階段が与えられ、各ステップで $1$ 段または $2$ 段上ることができます。**ただし、連続する 2 回で $1$ 段ずつ上ることはできません**。頂上まで上る方法は何通りあるでしょうか。 -下の図に示すように、3段目に登る実行可能な選択肢は2つだけで、1段を3回連続でジャンプする選択肢は制約条件を満たさないため破棄されます。 +下図に示すように、第 $3$ 段まで上る実行可能な方法は $2$ 通りしか残りません。そのうち、$1$ 段ずつ 3 回連続で上る方法は制約を満たさないため除外されます。 -![制約付きで3段目に登る実行可能な選択肢の数](dp_problem_features.assets/climbing_stairs_constraint_example.png) +![制約付きで第 3 段まで上る方法数](dp_problem_features.assets/climbing_stairs_constraint_example.png) -この問題では、前回が1段ジャンプだった場合、次回は必ず2段ジャンプでなければなりません。これは**次のステップの選択が現在の状態(現在の階段段数)だけでは独立して決定できず、前の状態(前回の階段段数)にも依存する**ことを意味します。 +この問題では、前回が $1$ 段上りだった場合、次回は必ず $2$ 段上らなければなりません。これは、**次の一手が現在の状態(現在いる階段の段数)だけでは独立に決まらず、一つ前の状態(前回いた段数)にも関係する**ことを意味します。 -この問題がもはや無記憶性を満たさず、状態遷移方程式 $dp[i] = dp[i-1] + dp[i-2]$ も失敗することは容易にわかります。なぜなら $dp[i-1]$ は今回の1段ジャンプを表しますが、多くの「前回が1段ジャンプだった」選択肢を含んでおり、制約を満たすためにはこれらを直接 $dp[i]$ に含めることはできません。 +容易に分かるように、この問題はもはや無後効性を満たしておらず、状態遷移方程式 $dp[i] = dp[i-1] + dp[i-2]$ も成立しません。というのも、$dp[i-1]$ は今回 $1$ 段上る場合を表しますが、その中には「前回も $1$ 段上ってきた」方法が多数含まれており、制約を満たすためには $dp[i-1]$ をそのまま $dp[i]$ に加えることができないからです。 -このため、状態定義を拡張する必要があります:**状態 $[i, j]$ は $i$ 段目にいて、前回が $j$ 段ジャンプだったことを表す**。ここで $j \in \{1, 2\}$ です。この状態定義は前回が1段ジャンプだったか2段ジャンプだったかを効果的に区別し、現在の状態がどこから来たかを適切に判断できます。 +このため、状態定義を拡張する必要があります。**状態 $[i, j]$ は、第 $i$ 段にいて前回に $j$ 段上ったことを表す**とし、ここで $j \in \{1, 2\}$ です。この状態定義により、前回が $1$ 段上りか $2$ 段上りかを有効に区別でき、現在の状態がどこから来たのかを判断できます。 -- 前回が1段ジャンプだった場合、前々回は必ず2段ジャンプを選択していたはずです。つまり、$dp[i, 1]$ は $dp[i-1, 2]$ からのみ遷移できます。 -- 前回が2段ジャンプだった場合、前々回は1段ジャンプまたは2段ジャンプを選択できました。つまり、$dp[i, 2]$ は $dp[i-2, 1]$ または $dp[i-2, 2]$ から遷移できます。 +- 前回に $1$ 段上った場合、その前の回は $2$ 段上りしか選べないため、$dp[i, 1]$ は $dp[i-1, 2]$ からのみ遷移できます。 +- 前回に $2$ 段上った場合、その前の回は $1$ 段上りまたは $2$ 段上りを選べるため、$dp[i, 2]$ は $dp[i-2, 1]$ または $dp[i-2, 2]$ から遷移できます。 -下の図に示すように、$dp[i, j]$ は状態 $[i, j]$ の解の数を表します。この時点で、状態遷移方程式は次のようになります: +下図に示すように、この定義のもとでは $dp[i, j]$ は状態 $[i, j]$ に対応する方法数を表します。このとき状態遷移方程式は次のようになります。 $$ \begin{cases} @@ -82,20 +82,20 @@ dp[i, 2] = dp[i-2, 1] + dp[i-2, 2] \end{cases} $$ -![制約を考慮した再帰関係](dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png) +![制約を考慮した漸化関係](dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png) -最終的に、$dp[n, 1] + dp[n, 2]$ を返せばよく、この2つの合計が $n$ 段目に登る解の総数を表します: +最終的に、$dp[n, 1] + dp[n, 2]$ を返せば十分であり、その和が第 $n$ 段まで上る方法の総数を表します。 ```src [file]{climbing_stairs_constraint_dp}-[class]{}-[func]{climbing_stairs_constraint_dp} ``` -上記のケースでは、前の状態のみを考慮すればよいため、状態定義を拡張することで依然として無記憶性を満たすことができます。しかし、一部の問題では非常に深刻な「状態効果」があります。 +上の例では、追加で考慮すべきなのは一つ前の状態だけであるため、状態定義を拡張することで問題を再び無後効性に適合させることができます。しかし、問題によっては非常に強い「後効性」があります。 -!!! question "障害物生成付き階段登り" +!!! question "階段昇りと障害物生成" - $n$ 段の階段があり、毎回1段または2段上ることができます。**$i$ 段目に登ったとき、システムが自動的に $2i$ 段目に障害物を置き、その後のすべてのラウンドで $2i$ 段目にジャンプすることが禁止される**と規定されています。例えば、最初の2ラウンドで2段目と3段目にジャンプした場合、その後は4段目と6段目にジャンプできません。頂上に登る方法は何通りありますか? + 全部で $n$ 段ある階段が与えられ、各ステップで $1$ 段または $2$ 段上ることができます。**第 $i$ 段に到達すると、システムは自動的に第 $2i$ 段に障害物を置き、それ以降はどの回でも第 $2i$ 段へ跳ぶことができない**とします。例えば、最初の 2 回でそれぞれ第 $2$ 段、第 $3$ 段に到達した場合、その後は第 $4$ 段と第 $6$ 段に跳ぶことはできません。頂上まで上る方法は何通りあるでしょうか。 -この問題では、次のジャンプはすべての過去の状態に依存します。各ジャンプがより高い段に障害物を置き、将来のジャンプに影響するからです。このような問題では、動的プログラミングはしばしば解決に苦労します。 +この問題では、次の跳躍が過去のすべての状態に依存します。なぜなら、各跳躍がより高い段に障害物を設置し、将来の跳躍に影響するからです。この種の問題は、動的計画法では解きにくいことが多いです。 -実際、多くの複雑な組み合わせ最適化問題(巡回セールスマン問題など)は無記憶性を満たしません。このような問題に対しては、通常、ヒューリスティック探索、遺伝的アルゴリズム、強化学習などの他の方法を選択して、限られた時間内に使用可能な局所最適解を得ます。 +実際、多くの複雑な組合せ最適化問題(例えば巡回セールスマン問題)は無後効性を満たしません。このような問題に対しては、通常、ヒューリスティック探索、遺伝的アルゴリズム、強化学習などの他の方法を用いて、限られた時間内に実用的な局所最適解を得ます。 diff --git a/ja/docs/chapter_dynamic_programming/dp_solution_pipeline.md b/ja/docs/chapter_dynamic_programming/dp_solution_pipeline.md index e974a47a3..02fe03192 100644 --- a/ja/docs/chapter_dynamic_programming/dp_solution_pipeline.md +++ b/ja/docs/chapter_dynamic_programming/dp_solution_pipeline.md @@ -1,65 +1,65 @@ -# 動的プログラミング問題解決アプローチ +# 動的計画法の問題解決の考え方 -前の2つのセクションでは、動的プログラミング問題の主要な特徴を紹介しました。次に、より実用的な2つの問題を一緒に探索しましょう。 +前の 2 節では動的計画法の問題の主要な特徴を紹介しました。ここからは、さらに実用的な 2 つの問題を一緒に考えていきます。 -1. 問題が動的プログラミング問題かどうかをどのように判断するか? -2. 動的プログラミング問題を解決する完全なステップは何か? +1. ある問題が動的計画法の問題かどうかを、どのように判断すればよいでしょうか? +2. 動的計画法の問題を解くには、どこから着手し、完全な手順はどのようなものでしょうか? ## 問題の判定 -一般的に言えば、問題が重複する部分問題、最適部分構造を含み、無記憶性を示す場合、通常動的プログラミング解法に適しています。しかし、問題の説明から直接これらの特徴を抽出することはしばしば困難です。したがって、通常は条件を緩和し、**まず問題がバックトラッキング(全探索)を使用した解決に適しているかどうかを観察**します。 +一般に、ある問題が重複部分問題と最適部分構造を含み、さらに無後效性を満たしているなら、通常は動的計画法で解くのに適しています。しかし、問題文からこれらの性質を直接読み取るのは簡単ではありません。そのため通常は条件を少し緩めて、**まずその問題がバックトラッキング(全探索)で解くのに適しているか**を観察します。 -**バックトラッキングに適した問題は通常「決定木モデル」に適合**し、これは木構造を使用して記述でき、各ノードは決定を表し、各パスは決定のシーケンスを表します。 +**バックトラッキングで解くのに適した問題は、通常「決定木モデル」を満たします**。この種の問題は木構造で表現でき、各ノードは 1 つの決定を表し、各経路は 1 つの決定列を表します。 -言い換えると、問題が明示的な決定概念を含み、解が一連の決定を通じて生成される場合、それは決定木モデルに適合し、通常バックトラッキングを使用して解決できます。 +言い換えると、問題に明確な決定の概念が含まれており、解が一連の決定によって生成されるなら、その問題は決定木モデルを満たし、通常はバックトラッキングで解くことができます。 -この基礎の上で、動的プログラミング問題を判定するための「ボーナスポイント」があります。 +これに加えて、動的計画法の問題には判定のための「加点要素」もあります。 -- 問題に最大化(最小化)または最も(最も少ない)最適な解を見つけるという記述が含まれている。 -- 問題の状態がリスト、多次元行列、または木を使用して表現でき、状態がその周囲の状態と再帰関係を持っている。 +- 問題文に最大(最小)や最多(最少)などの最適化に関する記述がある。 +- 問題の状態が配列、多次元行列、または木で表現でき、ある状態とその周辺の状態の間に漸化的な関係がある。 -対応して、「ペナルティポイント」もあります。 +反対に、「減点要素」もあります。 -- 問題の目標は最適解だけでなく、すべての可能な解を見つけることである。 -- 問題の説明に順列と組み合わせの明らかな特徴があり、特定の複数の解を返す必要がある。 +- 問題の目的が最適解を求めることではなく、あり得るすべての解を列挙することである。 +- 問題文に明確な順列・組合せの特徴があり、具体的な複数の解を返す必要がある。 -問題が決定木モデルに適合し、比較的明らかな「ボーナスポイント」を持つ場合、それが動的プログラミング問題であると仮定し、解決プロセス中に検証できます。 +ある問題が決定木モデルを満たし、さらに比較的明確な「加点要素」を備えているなら、その問題は動的計画法の問題であると仮定し、解く過程でそれを検証できます。 -## 問題解決ステップ +## 問題を解く手順 -動的プログラミング問題解決プロセスは問題の性質と難易度によって異なりますが、一般的に次のステップに従います:決定の記述、状態の定義、$dp$ テーブルの確立、状態遷移方程式の導出、境界条件の決定など。 +動的計画法の解法の流れは問題の性質や難易度によって異なりますが、通常は次の手順に従います。すなわち、決定を記述し、状態を定義し、$dp$ テーブルを構築し、状態遷移方程式を導出し、境界条件を定めます。 -問題解決ステップをより具体的に説明するために、古典的な問題「最小経路和」を例として使用します。 +解法の手順をより具体的に示すために、ここでは古典的な問題である「最小経路和」を例にします。 !!! question - $n \times m$ の二次元グリッド `grid` が与えられ、グリッドの各セルには負でない整数が含まれ、そのセルのコストを表します。ロボットは左上のセルから始まり、各ステップで下または右にのみ移動でき、右下のセルに到達するまで続けます。左上から右下への最小経路和を返してください。 + $n \times m$ の 2 次元グリッド `grid` が与えられます。グリッドの各セルには非負整数が格納されており、そのセルのコストを表します。ロボットは左上のセルを始点とし、毎回下または右に 1 マスだけ移動して、右下のセルまで進みます。左上から右下までの最小経路和を返してください。 -下の図は例を示しており、与えられたグリッドの最小経路和は $13$ です。 +次の図は 1 つの例を示しており、このグリッドの最小経路和は $13$ です。 -![最小経路和の例データ](dp_solution_pipeline.assets/min_path_sum_example.png) +![最小経路和のサンプルデータ](dp_solution_pipeline.assets/min_path_sum_example.png) -**第1ステップ:各ラウンドの決定を考え、状態を定義し、それにより $dp$ テーブルを得る** +**ステップ 1:各ラウンドの決定を考え、状態を定義して、$dp$ テーブルを得る** -この問題の各ラウンドの決定は、現在のセルから下または右に1ステップ移動することです。現在のセルの行と列のインデックスが $[i, j]$ であると仮定すると、下または右に移動した後、インデックスは $[i+1, j]$ または $[i, j+1]$ になります。したがって、状態には2つの変数が含まれるべきです:行インデックスと列インデックス、$[i, j]$ と表記されます。 +この問題における各ラウンドの決定は、現在のマスから下または右へ 1 マス進むことです。現在のマスの行・列インデックスを $[i, j]$ とすると、下または右へ 1 マス進んだ後のインデックスは $[i+1, j]$ または $[i, j+1]$ になります。したがって、状態には行インデックスと列インデックスの 2 つの変数を含め、$[i, j]$ と表します。 -状態 $[i, j]$ は部分問題に対応します:開始点 $[0, 0]$ から $[i, j]$ への最小経路和、$dp[i, j]$ と表記されます。 +状態 $[i, j]$ に対応する部分問題は、始点 $[0, 0]$ から $[i, j]$ まで進む最小経路和であり、その解を $dp[i, j]$ と記します。 -このようにして、下の図に示す二次元 $dp$ 行列を得ます。そのサイズは入力グリッド $grid$ と同じです。 +これで、次の図に示す 2 次元の $dp$ 行列が得られます。そのサイズは入力グリッド $grid$ と同じです。 -![状態定義とDPテーブル](dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png) +![状態の定義と dp テーブル](dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png) !!! note - 動的プログラミングとバックトラッキングは決定のシーケンスとして記述でき、状態はすべての決定変数から構成されます。問題解決の進行を記述するすべての変数を含むべきで、次の状態を導出するのに十分な情報を含んでいる必要があります。 + 動的計画法とバックトラッキングの過程は、いずれも 1 つの決定列として記述できます。そして状態は、すべての決定変数から構成されます。状態には解法の進行状況を表すすべての変数が含まれているべきであり、次の状態を導くのに十分な情報を持っている必要があります。 + + 各状態は 1 つの部分問題に対応しており、すべての部分問題の解を保存するために $dp$ テーブルを定義します。状態の各独立変数は、$dp$ テーブルの 1 つの次元に対応します。本質的に、$dp$ テーブルは状態と部分問題の解との対応関係です。 - 各状態は部分問題に対応し、すべての部分問題の解を保存するための $dp$ テーブルを定義します。状態の各独立変数は $dp$ テーブルの次元です。本質的に、$dp$ テーブルは状態と部分問題の解の間のマッピングです。 +**ステップ 2:最適部分構造を見つけ、状態遷移方程式を導出する** -**第2ステップ:最適部分構造を特定し、状態遷移方程式を導出する** +状態 $[i, j]$ は、上のマス $[i-1, j]$ または左のマス $[i, j-1]$ からしか遷移してきません。したがって最適部分構造は、$[i, j]$ に到達する最小経路和が、$[i, j-1]$ の最小経路和と $[i-1, j]$ の最小経路和のうち小さい方によって決まる、ということです。 -状態 $[i, j]$ について、それは上のセル $[i-1, j]$ または左のセル $[i, j-1]$ からのみ導出できます。したがって、最適部分構造は:$[i, j]$ に到達する最小経路和は、$[i, j-1]$ と $[i-1, j]$ の最小経路和の小さい方によって決定されます。 - -上記の分析に基づいて、下の図に示す状態遷移方程式を導出できます: +以上の分析から、次の図に示す状態遷移方程式を導くことができます。 $$ dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j] @@ -69,75 +69,75 @@ $$ !!! note - 定義された $dp$ テーブルに基づいて、元の問題と部分問題の関係を考え、部分問題の最適解から元の問題の最適解をどのように構築するか、つまり最適部分構造を見つけます。 + 定義済みの $dp$ テーブルに基づいて、元の問題と部分問題の関係を考え、部分問題の最適解から元の問題の最適解を構成する方法、すなわち最適部分構造を見つけます。 - 最適部分構造を特定したら、それを使用して状態遷移方程式を構築できます。 + ひとたび最適部分構造が見つかれば、それを使って状態遷移方程式を構築できます。 -**第3ステップ:境界条件と状態遷移順序を決定する** +**ステップ 3:境界条件と状態遷移の順序を決める** -この問題では、最初の行の状態は左の状態からのみ来ることができ、最初の列の状態は上の状態からのみ来ることができるため、最初の行 $i = 0$ と最初の列 $j = 0$ が境界条件です。 +この問題では、先頭行にある状態は左の状態からしか得られず、先頭列にある状態は上の状態からしか得られません。したがって、先頭行 $i = 0$ と先頭列 $j = 0$ が境界条件になります。 -下の図に示すように、各セルは左のセルと上のセルから導出されるため、ループを使用して行列を走査し、外側のループは行を反復し、内側のループは列を反復します。 +次の図に示すように、各マスは左のマスと上のマスから遷移してくるため、ループを用いて行列を走査します。外側のループで各行を、内側のループで各列を走査します。 -![境界条件と状態遷移順序](dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png) +![境界条件と状態遷移の順序](dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png) !!! note - 境界条件は動的プログラミングで $dp$ テーブルを初期化するために使用され、探索では枝刈りに使用されます。 + 境界条件は、動的計画法では $dp$ テーブルの初期化に使われ、探索では枝刈りに使われます。 + + 状態遷移の順序で重要なのは、現在の問題の解を計算するときに、それが依存するより小さな部分問題の解がすべてすでに正しく計算済みであることを保証する点です。 - 状態遷移順序の核心は、現在の問題の解を計算するとき、それが依存するすべての小さな部分問題が既に正しく計算されていることを確保することです。 +以上の分析により、すでに動的計画法のコードを直接書くことができます。しかし、部分問題への分解はトップダウンの考え方であるため、「力任せ探索 $\rightarrow$ メモ化探索 $\rightarrow$ 動的計画法」の順に実装するほうが、思考の流れにはより自然です。 -上記の分析に基づいて、動的プログラミングコードを直接書くことができます。しかし、部分問題の分解はトップダウンアプローチであるため、「力任せ探索 → メモ化探索 → 動的プログラミング」の順序で実装することが習慣的な思考により適合します。 +### 方法 1:力任せ探索 -### 方法1:力任せ探索 +状態 $[i, j]$ から探索を開始し、より小さな状態 $[i-1, j]$ と $[i, j-1]$ へと分解していきます。再帰関数には次の要素が含まれます。 -状態 $[i, j]$ から探索を開始し、それを常により小さな状態 $[i-1, j]$ と $[i, j-1]$ に分解します。再帰関数には以下の要素が含まれます。 - -- **再帰パラメータ**:状態 $[i, j]$。 -- **戻り値**:$[0, 0]$ から $[i, j]$ への最小経路和 $dp[i, j]$。 +- **再帰引数**:状態 $[i, j]$ 。 +- **戻り値**:$[0, 0]$ から $[i, j]$ までの最小経路和 $dp[i, j]$ 。 - **終了条件**:$i = 0$ かつ $j = 0$ のとき、コスト $grid[0, 0]$ を返す。 -- **枝刈り**:$i < 0$ または $j < 0$ でインデックスが範囲外のとき、コスト $+\infty$ を返し、実行不可能性を表す。 +- **枝刈り**:$i < 0$ または $j < 0$ でインデックスが範囲外になった場合、コスト $+\infty$ を返し、実行不可能であることを表す。 -実装コードは以下の通りです: +実装コードは次のとおりです。 ```src [file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs} ``` -下の図は $dp[2, 1]$ を根とする再帰木を示しており、いくつかの重複する部分問題を含み、その数はグリッド `grid` のサイズが増加すると急激に増加します。 +次の図は、$dp[2, 1]$ を根ノードとする再帰木を示しています。この中にはいくつかの重複部分問題が含まれており、その数はグリッド `grid` のサイズが大きくなるにつれて急激に増加します。 -本質的に、重複する部分問題の理由は:**左上隅から特定のセルに到達する複数のパスが存在する**ことです。 +本質的に、重複部分問題が生じる理由は、**左上からあるセルへ到達する経路が複数存在すること**にあります。 ![力任せ探索の再帰木](dp_solution_pipeline.assets/min_path_sum_dfs.png) -各状態には下と右の2つの選択があるため、左上隅から右下隅までの総ステップ数は $m + n - 2$ で、最悪時間計算量は $O(2^{m + n})$ です。この計算方法はグリッドエッジ近くの状況を考慮していないことに注意してください。ネットワークエッジに到達したとき、選択肢が1つしか残らないため、実際のパス数はより少なくなります。 +各状態には下と右の 2 通りの選択肢があり、左上から右下まで進むには合計で $m + n - 2$ 歩必要です。したがって最悪時間計算量は $O(2^{m + n})$ です。ここで、$n$ と $m$ はそれぞれグリッドの行数と列数を表します。なお、この見積もりではグリッド境界付近の状況を考慮していません。境界に達すると選択肢は 1 つだけになるため、実際の経路数はこれより少なくなります。 -### 方法2:メモ化探索 +### 方法 2:メモ化探索 -グリッド `grid` と同じサイズのメモリスト `mem` を導入し、様々な部分問題の解を記録し、重複する部分問題を枝刈りします: +グリッド `grid` と同じサイズのメモ配列 `mem` を導入し、各部分問題の解を記録して、重複部分問題を枝刈りします。 ```src [file]{min_path_sum}-[class]{}-[func]{min_path_sum_dfs_mem} ``` -下の図に示すように、メモ化を導入した後、すべての部分問題の解は一度だけ計算される必要があるため、時間計算量は状態の総数、つまりグリッドサイズ $O(nm)$ に依存します。 +次の図に示すように、メモ化を導入すると、すべての部分問題の解は 1 回だけ計算すればよくなります。したがって時間計算量は状態総数、すなわちグリッドサイズの $O(nm)$ に依存します。 ![メモ化探索の再帰木](dp_solution_pipeline.assets/min_path_sum_dfs_mem.png) -### 方法3:動的プログラミング +### 方法 3:動的計画法 -動的プログラミング解法を反復的に実装します。コードは以下の通りです: +反復に基づいて動的計画法の解法を実装すると、コードは次のようになります。 ```src [file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp} ``` -下の図は最小経路和の状態遷移プロセスを示し、グリッド全体を走査するため、**時間計算量は $O(nm)$** です。 +次の図は最小経路和の状態遷移の過程を示しています。グリッド全体を走査するため、**時間計算量は $O(nm)$** です。 配列 `dp` のサイズは $n \times m$ であるため、**空間計算量は $O(nm)$** です。 === "<1>" - ![最小経路和の動的プログラミングプロセス](dp_solution_pipeline.assets/min_path_sum_dp_step1.png) + ![最小経路和の動的計画法の過程](dp_solution_pipeline.assets/min_path_sum_dp_step1.png) === "<2>" ![min_path_sum_dp_step2](dp_solution_pipeline.assets/min_path_sum_dp_step2.png) @@ -174,9 +174,9 @@ $$ ### 空間最適化 -各セルは左と上のセルのみに関連するため、単一行配列を使用して $dp$ テーブルを実装できます。 +各マスは左のマスと上のマスにのみ関係するため、1 行の配列だけを使って $dp$ テーブルを実装できます。 -配列 `dp` は1行の状態のみを表現できるため、最初の列の状態を事前に初期化できず、各行を走査するときに更新することに注意してください: +ただし、配列 `dp` は 1 行分の状態しか表せないため、先頭列の状態を事前に初期化することはできず、各行を走査するときに更新する必要があります。 ```src [file]{min_path_sum}-[class]{}-[func]{min_path_sum_dp_comp} diff --git a/ja/docs/chapter_dynamic_programming/edit_distance_problem.md b/ja/docs/chapter_dynamic_programming/edit_distance_problem.md index 37ed6f3de..63fff1ed0 100644 --- a/ja/docs/chapter_dynamic_programming/edit_distance_problem.md +++ b/ja/docs/chapter_dynamic_programming/edit_distance_problem.md @@ -1,80 +1,80 @@ # 編集距離問題 -編集距離は、レーベンシュタイン距離とも呼ばれ、一つの文字列を別の文字列に変換するために必要な最小修正回数を指し、情報検索や自然言語処理で2つのシーケンス間の類似度を測定するためによく使用されます。 +編集距離は、Levenshtein 距離とも呼ばれ、2つの文字列の相互変換に必要な最小の編集回数を指し、通常は情報検索や自然言語処理において2つの系列の類似度を測るために用いられます。 !!! question - 2つの文字列 $s$ と $t$ が与えられたとき、$s$ を $t$ に変換するために必要な最小編集回数を返してください。 + 2つの文字列 $s$ と $t$ を入力し、$s$ を $t$ に変換するのに必要な最小編集回数を返してください。 + + 1つの文字列に対して3種類の編集操作を行えます。1文字の挿入、1文字の削除、任意の文字への置換です。 - 文字列に対して3種類の編集を実行できます:文字の挿入、文字の削除、または文字を他の任意の文字に置換。 +下図に示すように、`kitten` を `sitting` に変換するには 3 回の編集が必要で、内訳は 2 回の置換と 1 回の挿入です。`hello` を `algo` に変換する場合も 3 回必要で、内訳は 2 回の置換と 1 回の削除です。 -下の図に示すように、`kitten` を `sitting` に変換するには3回の編集が必要で、2回の置換と1回の挿入を含みます。`hello` を `algo` に変換するには3ステップが必要で、2回の置換と1回の削除を含みます。 +![編集距離のサンプルデータ](edit_distance_problem.assets/edit_distance_example.png) -![編集距離の例データ](edit_distance_problem.assets/edit_distance_example.png) +**編集距離問題は決定木モデルで自然に説明できます**。文字列が木のノードに対応し、1回の決定(1回の編集操作)が木の1本の辺に対応します。 -**編集距離問題は決定木モデルで自然に説明できます**。文字列は木のノードに対応し、1ラウンドの決定(編集操作)は木のエッジに対応します。 +下図に示すように、操作に制限がない場合、各ノードからは多くの辺を派生でき、それぞれの辺が1種類の操作に対応します。これは `hello` から `algo` への変換に多くの経路があり得ることを意味します。 -下の図に示すように、操作に制限がない場合、各ノードは多くのエッジを導出でき、それぞれが1つの操作に対応するため、`hello` を `algo` に変換する可能な経路は多数あります。 +決定木の観点から見ると、本問の目標はノード `hello` とノード `algo` の間の最短経路を求めることです。 -決定木の観点から、この問題の目標は、ノード `hello` とノード `algo` の間の最短経路を見つけることです。 +![決定木モデルに基づく編集距離問題の表現](edit_distance_problem.assets/edit_distance_decision_tree.png) -![決定木モデルに基づいて表現された編集距離問題](edit_distance_problem.assets/edit_distance_decision_tree.png) +### 動的計画法の考え方 -### 動的プログラミングアプローチ +**第1ステップ:各ラウンドの決定を考え、状態を定義して、$dp$ テーブルを得る** -**ステップ1:各ラウンドの決定を考え、状態を定義し、それにより $dp$ テーブルを得る** +各ラウンドの決定は、文字列 $s$ に対して1回の編集操作を行うことです。 -各ラウンドの決定は、文字列 $s$ に対して1つの編集操作を実行することを含みます。 +編集操作の過程で問題の規模が徐々に小さくなることを期待します。そうして初めて部分問題を構築できます。文字列 $s$ と $t$ の長さをそれぞれ $n$ と $m$ とし、まず両文字列の末尾の文字 $s[n-1]$ と $t[m-1]$ を考えます。 -編集プロセス中に問題のサイズを段階的に縮小することを目指し、これにより部分問題を構築できます。文字列 $s$ と $t$ の長さをそれぞれ $n$ と $m$ とします。まず、両方の文字列の末尾文字 $s[n-1]$ と $t[m-1]$ を考慮します。 +- $s[n-1]$ と $t[m-1]$ が同じなら、それらをスキップして、直接 $s[n-2]$ と $t[m-2]$ を考えます。 +- $s[n-1]$ と $t[m-1]$ が異なるなら、$s$ に対して1回の編集(挿入、削除、置換)を行い、両文字列の末尾の文字を同じにします。そうすることでそれらをスキップし、より小さい問題を考えられます。 -- $s[n-1]$ と $t[m-1]$ が同じ場合、それらをスキップして直接 $s[n-2]$ と $t[m-2]$ を考慮できます。 -- $s[n-1]$ と $t[m-1]$ が異なる場合、$s$ に対して1つの編集(挿入、削除、置換)を実行して、2つの文字列の末尾文字を一致させ、それらをスキップしてより小規模な問題を考慮できるようにする必要があります。 +つまり、文字列 $s$ に対する各ラウンドの決定(編集操作)は、$s$ と $t$ における残りの未一致文字を変化させます。したがって、状態は現在 $s$ と $t$ で考えている第 $i$ と第 $j$ 文字とし、$[i, j]$ と記します。 -したがって、文字列 $s$ での各ラウンドの決定(編集操作)は、$s$ と $t$ でマッチされる残りの文字を変更します。したがって、状態は $s$ と $t$ で現在考慮されている $i$ 番目と $j$ 番目の文字であり、$[i, j]$ と表記されます。 +状態 $[i, j]$ に対応する部分問題は、**$s$ の先頭 $i$ 文字を $t$ の先頭 $j$ 文字に変換するのに必要な最小編集回数**です。 -状態 $[i, j]$ は部分問題に対応します:**$s$ の最初の $i$ 文字を $t$ の最初の $j$ 文字に変更するために必要な最小編集回数**。 +これにより、サイズが $(i+1) \times (j+1)$ の2次元 $dp$ テーブルが得られます。 -これから、サイズ $(i+1) \times (j+1)$ の二次元 $dp$ テーブルを得ます。 +**第2ステップ:最適部分構造を見つけ、状態遷移方程式を導く** -**ステップ2:最適部分構造を特定し、状態遷移方程式を導出する** +部分問題 $dp[i, j]$ を考えます。これに対応する2つの文字列の末尾文字は $s[i-1]$ と $t[j-1]$ であり、編集操作の違いに応じて下図の3つの場合に分けられます。 -部分問題 $dp[i, j]$ を考慮すると、これに対応する2つの文字列の末尾文字は $s[i-1]$ と $t[j-1]$ であり、下の図に示すように3つのシナリオに分けることができます。 - -1. $s[i-1]$ の後に $t[j-1]$ を追加すると、残りの部分問題は $dp[i, j-1]$ です。 -2. $s[i-1]$ を削除すると、残りの部分問題は $dp[i-1, j]$ です。 -3. $s[i-1]$ を $t[j-1]$ に置換すると、残りの部分問題は $dp[i-1, j-1]$ です。 +1. $s[i-1]$ の後ろに $t[j-1]$ を追加する。このとき残る部分問題は $dp[i, j-1]$ です。 +2. $s[i-1]$ を削除する。このとき残る部分問題は $dp[i-1, j]$ です。 +3. $s[i-1]$ を $t[j-1]$ に置き換える。このとき残る部分問題は $dp[i-1, j-1]$ です。 ![編集距離の状態遷移](edit_distance_problem.assets/edit_distance_state_transfer.png) -上記の分析に基づいて、最適部分構造を決定できます:$dp[i, j]$ の最小編集回数は、$dp[i, j-1]$、$dp[i-1, j]$、$dp[i-1, j-1]$ の中の最小値に編集ステップ $1$ を加えたものです。対応する状態遷移方程式は: +以上の分析から、最適部分構造は次のように得られます。$dp[i, j]$ の最小編集回数は、$dp[i, j-1]$、$dp[i-1, j]$、$dp[i-1, j-1]$ の3つのうち最小の編集回数に、今回の編集回数 $1$ を加えたものです。対応する状態遷移方程式は次のとおりです: $$ dp[i, j] = \min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1 $$ -注意してください。**$s[i-1]$ と $t[j-1]$ が同じ場合、現在の文字に対して編集は必要ありません**。この場合、状態遷移方程式は: +注意すべき点として、**$s[i-1]$ と $t[j-1]$ が同じ場合、現在の文字を編集する必要はありません**。この場合の状態遷移方程式は次のとおりです: $$ dp[i, j] = dp[i-1, j-1] $$ -**ステップ3:境界条件と状態遷移の順序を決定する** +**第3ステップ:境界条件と状態遷移の順序を決める** -両方の文字列が空の場合、編集回数は $0$ です。つまり、$dp[0, 0] = 0$ です。$s$ が空で $t$ が空でない場合、最小編集回数は $t$ の長さに等しく、つまり最初の行 $dp[0, j] = j$ です。$s$ が空でなく $t$ が空の場合、最小編集回数は $s$ の長さに等しく、つまり最初の列 $dp[i, 0] = i$ です。 +2つの文字列がともに空のとき、編集回数は $0$、すなわち $dp[0, 0] = 0$ です。$s$ が空で $t$ が空でないとき、最小編集回数は $t$ の長さに等しいため、先頭行は $dp[0, j] = j$ です。$s$ が空でなく $t$ が空のとき、最小編集回数は $s$ の長さに等しいため、先頭列は $dp[i, 0] = i$ です。 -状態遷移方程式を観察すると、$dp[i, j]$ の解決は左、上、左上の解に依存するため、二重ループを使用して正しい順序で $dp$ テーブル全体を走査できます。 +状態遷移方程式を観察すると、$dp[i, j]$ の解は左、上、左上の解に依存します。そのため、2重ループで $dp$ テーブル全体を順方向に走査すれば十分です。 -### コード実装 +### コードの実装 ```src [file]{edit_distance}-[class]{}-[func]{edit_distance_dp} ``` -下の図に示すように、編集距離問題の状態遷移プロセスはナップサック問題と非常に似ており、二次元グリッドを埋めることと見なすことができます。 +下図に示すように、編集距離問題の状態遷移の過程はナップサック問題と非常によく似ており、どちらも2次元グリッドを埋めていく過程とみなせます。 === "<1>" - ![編集距離の動的プログラミングプロセス](edit_distance_problem.assets/edit_distance_dp_step1.png) + ![編集距離の動的計画法の過程](edit_distance_problem.assets/edit_distance_dp_step1.png) === "<2>" ![edit_distance_dp_step2](edit_distance_problem.assets/edit_distance_dp_step2.png) @@ -120,9 +120,9 @@ $$ ### 空間最適化 -$dp[i, j]$ は上の $dp[i-1, j]$、左の $dp[i, j-1]$、左上の $dp[i-1, j-1]$ の解から導出され、直接走査では左上の解 $dp[i-1, j-1]$ が失われ、逆走査では事前に $dp[i, j-1]$ を構築できないため、どちらの走査順序も実行可能ではありません。 +$dp[i,j]$ は上の $dp[i-1, j]$、左の $dp[i, j-1]$、左上の $dp[i-1, j-1]$ から遷移されますが、順方向走査では左上の $dp[i-1, j-1]$ を失い、逆方向走査では $dp[i, j-1]$ を事前に構築できません。そのため、どちらの走査順序も適切ではありません。 -この理由で、変数 `leftup` を使用して左上の $dp[i-1, j-1]$ からの解を一時的に保存し、左と上の解のみを考慮すればよくなります。この状況は無制限ナップサック問題と似ており、直接走査が可能です。コードは以下の通りです: +そのため、変数 `leftup` を用いて左上の解 $dp[i-1, j-1]$ を一時保存し、左と上の解だけを考えればよくなります。このときの状況は完全ナップサック問題と同じであり、順方向走査を用いることができます。コードは次のとおりです: ```src [file]{edit_distance}-[class]{}-[func]{edit_distance_dp_comp} diff --git a/ja/docs/chapter_dynamic_programming/index.md b/ja/docs/chapter_dynamic_programming/index.md index ed680463c..dfb64425b 100644 --- a/ja/docs/chapter_dynamic_programming/index.md +++ b/ja/docs/chapter_dynamic_programming/index.md @@ -1,9 +1,9 @@ -# 動的プログラミング +# 動的計画法 -![動的プログラミング](../assets/covers/chapter_dynamic_programming.jpg) +![動的計画法](../assets/covers/chapter_dynamic_programming.jpg) !!! abstract - 川が流れて海に注ぐように、 - - 動的プログラミングは小さな問題の解を織り合わせて、より大きな問題の解へと導きます。一歩一歩進んで、最終的な答えが待つ彼岸へと向かいます。 + 小川は川へと注ぎ、河川は大海へと注ぐ。 + + 動的計画法は小さな問題の解を集めて大きな問題の答えとし、一歩ずつ私たちを問題解決の彼岸へと導く。 diff --git a/ja/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md b/ja/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md index bdf76d091..4f331a26a 100644 --- a/ja/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md +++ b/ja/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md @@ -1,110 +1,110 @@ -# 動的プログラミングの紹介 +# 動的計画法入門 -動的プログラミングは重要なアルゴリズムパラダイムであり、問題を一連の小さな部分問題に分解し、これらの部分問題の解を保存することで冗長な計算を避け、時間効率を大幅に向上させます。 +動的計画法(dynamic programming)は重要なアルゴリズムパラダイムであり、問題をより小さな部分問題の列に分解し、それらの解を保存して重複計算を避けることで、時間効率を大幅に向上させます。 -このセクションでは、古典的な問題から始めて、まず力任せの探索法による解法を提示し、重複する部分問題を特定してから、より効率的な動的プログラミング解法を段階的に導出します。 +本節では、古典的な例題から始めて、まずその力任せのバックトラッキング解法を示し、そこに含まれる重複部分問題を観察したうえで、より効率的な動的計画法の解法を段階的に導きます。 -!!! question "階段登り" +!!! question "階段を上る" - $n$ 段の階段があり、一度に $1$ 段または $2$ 段上ることができます。頂上に到達する方法は何通りありますか? + 全体で $n$ 段ある階段が与えられ、各ステップで $1$ 段または $2$ 段上ることができます。頂上まで到達する方法は何通りあるでしょうか? -下の図に示すように、$3$ 段の階段の頂上に到達する方法は $3$ 通りあります。 +次の図に示すように、$3$ 段の階段では、頂上まで到達する方法は全部で $3$ 通りあります。 -![3段目に到達する方法の数](intro_to_dynamic_programming.assets/climbing_stairs_example.png) +![3 段の階段を上る方法の数](intro_to_dynamic_programming.assets/climbing_stairs_example.png) -この問題は**バックトラッキングを用いてすべての可能性を網羅**することで方法の数を計算することを目的としています。具体的には、階段登りの問題を多段階選択プロセスとして考えます:地面から始めて、毎回 $1$ 段または $2$ 段上るかを選択し、階段の頂上に到達したら方法の数をカウントし、頂上を超えた場合はプルーニング(枝刈り)を行います。コードは以下の通りです: +この問題の目的は方法の総数を求めることです。**考えられるすべての可能性をバックトラッキングで総当たりすることができます**。具体的には、階段を上ることを複数ラウンドの選択過程とみなし、地面から出発して各ラウンドで $1$ 段または $2$ 段上ります。階段の頂上に到達するたびに方法数を $1$ 増やし、頂上を越えた場合は枝刈りします。コードは次のとおりです: ```src [file]{climbing_stairs_backtrack}-[class]{}-[func]{climbing_stairs_backtrack} ``` -## 方法1:力任せ探索 +## 方法 1:総当たり探索 -バックトラッキングアルゴリズムは問題を明示的に部分問題に分解しません。代わりに、問題を一連の決定ステップとして扱い、試行と枝刈りを通じてすべての可能性を探索します。 +バックトラッキング法は通常、問題を明示的に分解するのではなく、問題解決を一連の意思決定ステップとみなし、試行と枝刈りによってあらゆる可能な解を探索します。 -この問題を分解アプローチを使って分析できます。$dp[i]$ を $i$ 段目に到達する方法の数とします。この場合、$dp[i]$ が元の問題であり、その部分問題は次のようになります: +この問題を問題分解の観点から分析してみましょう。$i$ 段目まで上る方法が全部で $dp[i]$ 通りあるとすると、$dp[i]$ が元の問題であり、その部分問題には次が含まれます: $$ dp[i-1], dp[i-2], \dots, dp[2], dp[1] $$ -各移動は $1$ 段または $2$ 段しか進めないため、$i$ 段目に立っているとき、前のステップは $i-1$ 段目または $i-2$ 段目のいずれかにいたはずです。つまり、$i$ 段目には $i-1$ 段目または $i-2$ 段目からしか到達できません。 +各ラウンドでは $1$ 段または $2$ 段しか上れないため、$i$ 段目の階段に立っているとき、直前のラウンドでは $i - 1$ 段目または $i - 2$ 段目にしか立てません。言い換えると、$i -1$ 段目または $i - 2$ 段目からしか $i$ 段目へ進めません。 -これにより重要な結論が得られます:**$i-1$ 段目に到達する方法の数に $i-2$ 段目に到達する方法の数を加えたものが、$i$ 段目に到達する方法の数に等しい**。式は以下の通りです: +ここから重要な帰結が得られます。**$i - 1$ 段目まで上る方法数と $i - 2$ 段目まで上る方法数の和が、$i$ 段目まで上る方法数に等しい**のです。式は次のとおりです: $$ dp[i] = dp[i-1] + dp[i-2] $$ -これは、階段登り問題において部分問題間に再帰関係があることを意味し、**元の問題の解は部分問題の解から構築できます**。下の図はこの再帰関係を示しています。 +これは、階段を上る問題では各部分問題の間に漸化関係があり、**元の問題の解は部分問題の解から構築できる**ことを意味します。次の図はこの漸化関係を示しています。 -![解の数の再帰関係](intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png) +![方法数の漸化関係](intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png) -再帰式に従って力任せ探索解法を得ることができます。$dp[n]$ から始めて、**より大きな問題を再帰的に2つの小さな部分問題の和に分解**し、解が既知の最小の部分問題 $dp[1]$ と $dp[2]$ に到達するまで続けます。$dp[1] = 1$ と $dp[2] = 2$ で、それぞれ1段目と2段目に登る方法が $1$ 通りと $2$ 通りあることを表します。 +漸化式に基づいて総当たり探索の解法を得ることができます。$dp[n]$ を出発点とし、**より大きな問題を再帰的に 2 つのより小さな問題の和へ分解**していき、最小部分問題 $dp[1]$ と $dp[2]$ に到達したら返します。ここで最小部分問題の解は既知であり、$dp[1] = 1$、$dp[2] = 2$ です。これは、第 $1$ 段目と第 $2$ 段目まで上る方法がそれぞれ $1$ 通り、$2$ 通りであることを表します。 -以下のコードを観察すると、標準的なバックトラッキングコードと同様に深さ優先探索に属しますが、より簡潔です: +次のコードを見ると、標準的なバックトラッキングコードと同じく深さ優先探索に属しますが、より簡潔です: ```src [file]{climbing_stairs_dfs}-[class]{}-[func]{climbing_stairs_dfs} ``` -下の図は力任せ探索によって形成される再帰木を示しています。問題 $dp[n]$ について、その再帰木の深さは $n$ で、時間計算量は $O(2^n)$ です。この指数的増加により、$n$ が大きいとプログラムの実行がはるかに遅くなり、長い待機時間が生じます。 +次の図は総当たり探索によって形成される再帰木を示しています。問題 $dp[n]$ に対して、その再帰木の深さは $n$、時間計算量は $O(2^n)$ です。指数オーダーは爆発的に増加するため、比較的大きな $n$ を入力すると長時間待たされることになります。 -![階段登りの再帰木](intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png) +![階段上りに対応する再帰木](intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png) -上の図を観察すると、**指数時間計算量は「重複する部分問題」によって引き起こされる**ことがわかります。例えば、$dp[9]$ は $dp[8]$ と $dp[7]$ に分解され、$dp[8]$ はさらに $dp[7]$ と $dp[6]$ に分解され、両方とも部分問題 $dp[7]$ を含んでいます。 +上の図を見ると、**指数オーダーの時間計算量は「重複部分問題」によって生じています**。たとえば $dp[9]$ は $dp[8]$ と $dp[7]$ に分解され、$dp[8]$ は $dp[7]$ と $dp[6]$ に分解されるため、どちらにも部分問題 $dp[7]$ が含まれています。 -このように、部分問題にはさらに小さな重複する部分問題が含まれ、これは無限に続きます。計算リソースの大部分がこれらの重複する部分問題に浪費されています。 +このように、部分問題の中にはさらに小さな重複部分問題が含まれ、それが際限なく続いていきます。計算資源の大部分は、こうした重複部分問題に浪費されています。 -## 方法2:メモ化探索 +## 方法 2:メモ化探索 -アルゴリズムの効率を向上させるため、**すべての重複する部分問題を一度だけ計算したい**と考えます。この目的のため、各部分問題の解を記録する配列 `mem` を宣言し、探索プロセス中に重複する部分問題を枝刈りします。 +アルゴリズム効率を高めるため、**すべての重複部分問題を 1 回だけ計算したい**と考えます。そのために、各部分問題の解を記録する配列 `mem` を宣言し、探索の過程で重複部分問題を枝刈りします。 -1. $dp[i]$ が初めて計算されるとき、後で使用するために `mem[i]` に記録します。 -2. $dp[i]$ を再度計算する必要があるとき、`mem[i]` から直接結果を取得でき、その部分問題の冗長な計算を避けられます。 +1. $dp[i]$ を初めて計算したとき、その結果を `mem[i]` に記録して後で使えるようにします。 +2. 再び $dp[i]$ を計算する必要が生じたときは、`mem[i]` から直接結果を取得し、その部分問題の重複計算を避けます。 -コードは以下の通りです: +コードは次のとおりです: ```src [file]{climbing_stairs_dfs_mem}-[class]{}-[func]{climbing_stairs_dfs_mem} ``` -下の図を観察すると、**メモ化後、すべての重複する部分問題は一度だけ計算される必要があり、時間計算量を $O(n)$ に最適化**します。これは大幅な改善です。 +次の図を見ると、**メモ化を行うことで、すべての重複部分問題は 1 回だけ計算すればよくなり、時間計算量は $O(n)$ まで改善されます**。これは大きな飛躍です。 -![メモ化探索による再帰木](intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png) +![メモ化探索に対応する再帰木](intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png) -## 方法3:動的プログラミング +## 方法 3:動的計画法 -**メモ化探索は「トップダウン」方式**です:元の問題(根ノード)から始めて、より大きな部分問題をより小さなものに再帰的に分解し、最小の既知の部分問題(葉ノード)の解に到達するまで続けます。その後、バックトラッキングにより部分問題の解を収集し、元の問題の解を構築します。 +**メモ化探索は「トップダウン」の方法**です。元の問題(根ノード)から始めて、より大きな部分問題を再帰的により小さな部分問題へ分解し、解が既知である最小部分問題(葉ノード)に至ります。その後、バックトラックしながら各層で部分問題の解を集め、元の問題の解を構築します。 -一方、**動的プログラミングは「ボトムアップ」方式**です:最小の部分問題の解から始めて、元の問題が解決されるまで、より大きな部分問題の解を反復的に構築します。 +これとは対照的に、**動的計画法は「ボトムアップ」の方法**です。最小部分問題の解から始めて、より大きな部分問題の解を反復的に構築し、最終的に元の問題の解を得ます。 -動的プログラミングはバックトラッキングを必要としないため、ループを使った反復のみが必要で、再帰は不要です。以下のコードでは、配列 `dp` を初期化して部分問題の解を保存し、メモ化探索の配列 `mem` と同じ記録機能を果たします: +動的計画法にはバックトラックの過程が含まれないため、再帰を使う必要はなく、ループによる反復だけで実装できます。次のコードでは、部分問題の解を保存する配列 `dp` を初期化しており、これはメモ化探索における配列 `mem` と同じ記録の役割を果たします: ```src [file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp} ``` -下の図は上記コードの実行プロセスをシミュレートしています。 +次の図は、以上のコードの実行過程をシミュレートしたものです。 -![階段登りの動的プログラミングプロセス](intro_to_dynamic_programming.assets/climbing_stairs_dp.png) +![階段上りの動的計画法の過程](intro_to_dynamic_programming.assets/climbing_stairs_dp.png) -バックトラッキングアルゴリズムと同様に、動的プログラミングも「状態」の概念を使用して問題解決の特定の段階を表現し、各状態は部分問題とその局所最適解に対応します。例えば、階段登り問題の状態は現在のステップ番号 $i$ として定義されます。 +バックトラッキング法と同様に、動的計画法でも問題解決の特定段階を表すために「状態」という概念を用います。各状態は 1 つの部分問題と、それに対応する局所最適解に対応します。たとえば、階段を上る問題では、状態は現在いる階段の段数 $i$ と定義されます。 -上記の内容に基づいて、動的プログラミングでよく使用される用語をまとめることができます。 +以上を踏まえると、動的計画法のよく使われる用語を次のようにまとめられます。 -- 配列 `dp` はDPテーブルと呼ばれ、$dp[i]$ は状態 $i$ に対応する部分問題の解を表します。 -- 最小の部分問題(ステップ $1$ と $2$)に対応する状態は初期状態と呼ばれます。 -- 再帰式 $dp[i] = dp[i-1] + dp[i-2]$ は状態遷移方程式と呼ばれます。 +- 配列 `dp` を dp テーブル と呼び、$dp[i]$ は状態 $i$ に対応する部分問題の解を表します。 +- 最小部分問題に対応する状態(第 $1$ 段目と第 $2$ 段目の階段)を初期状態と呼びます。 +- 漸化式 $dp[i] = dp[i-1] + dp[i-2]$ を状態遷移方程式と呼びます。 ## 空間最適化 -注意深い読者は**$dp[i]$ は $dp[i-1]$ と $dp[i-2]$ のみに関連するため、すべての部分問題の解を保存するために配列 `dp` を使用する必要がない**ことに気づくでしょう。単に2つの変数を使って反復的に進めることができます。コードは以下の通りです: +注意深い読者は気づいたかもしれません。**$dp[i]$ は $dp[i-1]$ と $dp[i-2]$ にしか依存しないため、すべての部分問題の解を保存するために配列 `dp` を使う必要はありません**。2 つの変数を順に更新していくだけで十分です。コードは次のとおりです: ```src [file]{climbing_stairs_dp}-[class]{}-[func]{climbing_stairs_dp_comp} ``` -上記のコードを観察すると、配列 `dp` が占有していた空間が削除されるため、空間計算量は $O(n)$ から $O(1)$ に削減されます。 +上のコードを見ると、配列 `dp` が占めていた領域を省けるため、空間計算量は $O(n)$ から $O(1)$ へと下がります。 -多くの動的プログラミング問題では、現在の状態は限られた数の前の状態のみに依存するため、必要な状態のみを保持し、「次元削減」によってメモリ空間を節約できます。**この空間最適化技術は「ローリング変数」または「ローリング配列」として知られています**。 +動的計画法の問題では、現在の状態はしばしば直前の限られた個数の状態にしか関係しません。このような場合は、必要な状態だけを保持し、「次元削減」によってメモリ空間を節約できます。**この空間最適化の技巧は「ローリング変数」または「ローリング配列」と呼ばれます**。 diff --git a/ja/docs/chapter_dynamic_programming/knapsack_problem.md b/ja/docs/chapter_dynamic_programming/knapsack_problem.md index c0dbf4b85..6c41cd752 100644 --- a/ja/docs/chapter_dynamic_programming/knapsack_problem.md +++ b/ja/docs/chapter_dynamic_programming/knapsack_problem.md @@ -1,97 +1,97 @@ -# 0-1ナップサック問題 +# 0-1 ナップサック問題 -ナップサック問題は動的プログラミングの優れた入門問題であり、動的プログラミングで最も一般的な問題タイプです。0-1ナップサック問題、無制限ナップサック問題、複数ナップサック問題など、多くの変種があります。 +ナップサック問題は、動的計画法の入門として非常に適した問題であり、動的計画法で最もよく見られる問題形式の1つです。これには 0-1 ナップサック問題、完全ナップサック問題、多重ナップサック問題など、多くの派生があります。 -このセクションでは、まず最も一般的な0-1ナップサック問題を解決します。 +本節では、まず最も一般的な 0-1 ナップサック問題を解いていきます。 !!! question - $n$ 個のアイテムが与えられ、$i$ 番目のアイテムの重量は $wgt[i-1]$ で値は $val[i-1]$ です。容量が $cap$ のナップサックがあります。各アイテムは1回のみ選択できます。容量制限下でナップサックに入れることができるアイテムの最大値は何ですか? + $n$ 個の品物が与えられ、$i$ 番目の品物の重さは $wgt[i-1]$、価値は $val[i-1]$ であり、容量 $cap$ のナップサックがあります。各品物は1回しか選べないとき、ナップサック容量の制約下で入れられる品物の最大価値を求めてください。 -下の図を観察すると、アイテム番号 $i$ は1から数え始め、配列インデックスは0から始まるため、アイテム $i$ の重量は $wgt[i-1]$ に対応し、値は $val[i-1]$ に対応します。 +以下の図を見てみましょう。品物番号 $i$ は $1$ から始まり、配列のインデックスは $0$ から始まるため、品物 $i$ は重さ $wgt[i-1]$、価値 $val[i-1]$ に対応します。 -![0-1ナップサックの例データ](knapsack_problem.assets/knapsack_example.png) +![0-1 ナップサックのサンプルデータ](knapsack_problem.assets/knapsack_example.png) -0-1ナップサック問題を $n$ ラウンドの決定から構成されるプロセスとして考えることができます。各アイテムについて入れない、または入れるという2つの決定があり、したがって問題は決定木モデルに適合します。 +0-1 ナップサック問題は、$n$ 回の意思決定からなる過程とみなせます。各品物について「入れない」「入れる」という2つの選択肢があるため、この問題は決定木モデルを満たします。 -この問題の目的は「限られた容量の下でナップサックに入れることができるアイテムの値を最大化する」ことであり、動的プログラミング問題である可能性が高いです。 +この問題の目的は「ナップサック容量の制約下で入れられる品物の最大価値」を求めることなので、動的計画法の問題である可能性が高いです。 -**第1ステップ:各ラウンドの決定を考え、状態を定義し、それにより $dp$ テーブルを得る** +**ステップ1:各ラウンドの選択を考え、状態を定義して、$dp$ テーブルを得る** -各アイテムについて、ナップサックに入れなければ容量は変わらず、入れれば容量は減少します。これから状態定義を得ることができます:現在のアイテム番号 $i$ とナップサック容量 $c$、$[i, c]$ と表記されます。 +各品物について、ナップサックに入れなければ容量は変わらず、入れれば容量は減少します。ここから状態を、現在の品物番号 $i$ とナップサック容量 $c$ として定義し、$[i, c]$ と表せます。 -状態 $[i, c]$ は部分問題に対応します:**容量 $c$ のナップサックでの最初の $i$ 個のアイテムの最大値**、$dp[i, c]$ と表記されます。 +状態 $[i, c]$ に対応する部分問題は、**先頭 $i$ 個の品物を容量 $c$ のナップサックに入れるときの最大価値** であり、これを $dp[i, c]$ と記します。 -探している解は $dp[n, cap]$ であるため、サイズ $(n+1) \times (cap+1)$ の二次元 $dp$ テーブルが必要です。 +求めるべきものは $dp[n, cap]$ なので、サイズ $(n+1) \times (cap+1)$ の2次元 $dp$ テーブルが必要です。 -**第2ステップ:最適部分構造を特定し、状態遷移方程式を導出する** +**ステップ2:最適部分構造を見つけ、状態遷移方程式を導く** -アイテム $i$ の決定を行った後、残るのは最初の $i-1$ 個のアイテムの決定の部分問題であり、これは2つのケースに分けることができます。 +品物 $i$ に対する選択を行った後に残るのは、先頭 $i-1$ 個の品物に対する部分問題であり、次の2つのケースに分けられます。 -- **アイテム $i$ を入れない**:ナップサック容量は変わらず、状態は $[i-1, c]$ に変わります。 -- **アイテム $i$ を入れる**:ナップサック容量は $wgt[i-1]$ だけ減少し、値は $val[i-1]$ だけ増加し、状態は $[i-1, c-wgt[i-1]]$ に変わります。 +- **品物 $i$ を入れない** :ナップサック容量は変わらず、状態は $[i-1, c]$ に変化します。 +- **品物 $i$ を入れる** :ナップサック容量は $wgt[i-1]$ だけ減少し、価値は $val[i-1]$ だけ増加して、状態は $[i-1, c-wgt[i-1]]$ に変化します。 -上記の分析により、この問題の最適部分構造が明らかになります:**最大値 $dp[i, c]$ は、アイテム $i$ を入れない方案とアイテム $i$ を入れる方案の2つのうち、より大きな値に等しい**。これから状態遷移方程式を導出できます: +以上の分析から、この問題の最適部分構造が分かります。すなわち、**最大価値 $dp[i, c]$ は、品物 $i$ を入れない場合と入れる場合のうち、より価値の大きい方に等しい** ということです。これにより、次の状態遷移方程式を導けます。 $$ dp[i, c] = \max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1]) $$ -現在のアイテムの重量 $wgt[i - 1]$ が残りのナップサック容量 $c$ を超える場合、唯一の選択肢はナップサックに入れないことであることに注意することが重要です。 +注意すべき点として、現在の品物の重さ $wgt[i - 1]$ が残りのナップサック容量 $c$ を超える場合は、入れない選択しかできません。 -**第3ステップ:境界条件と状態遷移の順序を決定する** +**ステップ3:境界条件と状態遷移の順序を決める** -アイテムがない場合またはナップサック容量が $0$ の場合、最大値は $0$ です。つまり、最初の列 $dp[i, 0]$ と最初の行 $dp[0, c]$ はどちらも $0$ に等しいです。 +品物がない場合、またはナップサック容量が $0$ の場合、最大価値は $0$ です。すなわち、先頭列 $dp[i, 0]$ と先頭行 $dp[0, c]$ はいずれも $0$ になります。 -現在の状態 $[i, c]$ は直接上の状態 $[i-1, c]$ と左上の状態 $[i-1, c-wgt[i-1]]$ から遷移するため、2層のループを通じて $dp$ テーブル全体を順序通りに走査します。 +現在の状態 $[i, c]$ は、上側の状態 $[i-1, c]$ と左上の状態 $[i-1, c-wgt[i-1]]$ から遷移してくるため、2重ループで $dp$ テーブル全体を順方向に走査すれば十分です。 -上記の分析に従って、次に力任せ探索、メモ化探索、動的プログラミングの順序で解法を実装します。 +以上の分析に基づき、次に全探索、メモ化探索、動的計画法の順で実装していきます。 -### 方法1:力任せ探索 +### 方法1:全探索 -探索コードには以下の要素が含まれます。 +探索コードには次の要素が含まれます。 -- **再帰パラメータ**:状態 $[i, c]$。 -- **戻り値**:部分問題 $dp[i, c]$ の解。 -- **終了条件**:アイテム番号が範囲外 $i = 0$ またはナップサックの残り容量が $0$ のとき、再帰を終了し値 $0$ を返す。 -- **枝刈り**:現在のアイテムの重量がナップサックの残り容量を超える場合、唯一の選択肢はナップサックに入れないことです。 +- **再帰引数**:状態 $[i, c]$ です。 +- **戻り値**:部分問題の解 $dp[i, c]$ です。 +- **終了条件**:品物番号が範囲外である $i = 0$、またはナップサックの残り容量が $0$ のとき、再帰を終了して価値 $0$ を返します。 +- **枝刈り**:現在の品物の重さがナップサックの残り容量を超える場合、入れない選択しかできません。 ```src [file]{knapsack}-[class]{}-[func]{knapsack_dfs} ``` -下の図に示すように、各アイテムは選択しないと選択するという2つの探索分岐を生成するため、時間計算量は $O(2^n)$ です。 +以下の図のように、各品物ごとに「選ばない」「選ぶ」の2つの探索分岐が生じるため、時間計算量は $O(2^n)$ です。 -再帰木を観察すると、$dp[1, 10]$ などの重複する部分問題があることが容易にわかります。アイテムが多く、ナップサック容量が大きい場合、特に同じ重量のアイテムが多い場合、重複する部分問題の数は大幅に増加します。 +再帰木を観察すると、$dp[1, 10]$ などの重複部分問題が存在することが分かります。品物数が多く、ナップサック容量が大きく、特に同じ重さの品物が多い場合には、重複部分問題の数は大幅に増加します。 -![0-1ナップサック問題の力任せ探索再帰木](knapsack_problem.assets/knapsack_dfs.png) +![0-1 ナップサック問題の全探索の再帰木](knapsack_problem.assets/knapsack_dfs.png) ### 方法2:メモ化探索 -重複する部分問題が一度だけ計算されることを確保するために、部分問題の解を記録するメモ化リスト `mem` を使用します。ここで `mem[i][c]` は $dp[i, c]$ に対応します。 +重複部分問題が一度だけ計算されるようにするため、メモ配列 `mem` を用いて部分問題の解を記録します。ここで `mem[i][c]` は $dp[i, c]$ に対応します。 -メモ化を導入した後、**時間計算量は部分問題の数に依存**し、$O(n \times cap)$ になります。実装コードは以下の通りです: +メモ化を導入すると、**時間計算量は部分問題の数に依存し**、すなわち $O(n \times cap)$ になります。実装コードは次のとおりです。 ```src [file]{knapsack}-[class]{}-[func]{knapsack_dfs_mem} ``` -下の図はメモ化探索で枝刈りされる探索分岐を示しています。 +次の図は、メモ化探索で剪定された探索分岐を示しています。 -![0-1ナップサック問題のメモ化探索再帰木](knapsack_problem.assets/knapsack_dfs_mem.png) +![0-1 ナップサック問題のメモ化探索の再帰木](knapsack_problem.assets/knapsack_dfs_mem.png) -### 方法3:動的プログラミング +### 方法3:動的計画法 -動的プログラミングは本質的に状態遷移中に $dp$ テーブルを埋めることを含みます。コードは下の図に示されています: +動的計画法の本質は、状態遷移に従って $dp$ テーブルを埋めていく過程です。コードは次のようになります。 ```src [file]{knapsack}-[class]{}-[func]{knapsack_dp} ``` -下の図に示すように、時間計算量と空間計算量の両方が配列 `dp` のサイズ、つまり $O(n \times cap)$ によって決定されます。 +以下の図のように、時間計算量と空間計算量はいずれも配列 `dp` のサイズによって決まり、$O(n \times cap)$ です。 === "<1>" - ![0-1ナップサック問題の動的プログラミングプロセス](knapsack_problem.assets/knapsack_dp_step1.png) + ![0-1 ナップサック問題の動的計画法の過程](knapsack_problem.assets/knapsack_dp_step1.png) === "<2>" ![knapsack_dp_step2](knapsack_problem.assets/knapsack_dp_step2.png) @@ -134,17 +134,17 @@ $$ ### 空間最適化 -各状態は上の行の状態のみに関連するため、2つの配列を使用してローリング前進させ、空間計算量を $O(n^2)$ から $O(n)$ に削減できます。 +各状態は直前の行の状態にしか依存しないため、2つの配列をローテーションして用いることで、空間計算量を $O(n^2)$ から $O(n)$ に削減できます。 -さらに考えてみると、1つの配列だけで空間最適化を達成できるでしょうか?各状態が直接上のセルまたは左上のセルから遷移することが観察できます。配列が1つしかない場合、$i$ 行目の走査を開始するとき、その配列はまだ $i-1$ 行目の状態を保存しています。 +さらに考えると、1つの配列だけで空間最適化を実現できるでしょうか。観察すると、各状態は真上または左上のマスから遷移してきます。配列が1つしかないと仮定すると、$i$ 行目の走査を開始した時点では、その配列にはまだ $i-1$ 行目の状態が格納されています。 -- 通常の順序で走査する場合、$dp[i, j]$ に走査したとき、左上の $dp[i-1, 1]$ ~ $dp[i-1, j-1]$ の値がすでに上書きされている可能性があり、正しい状態遷移結果を得ることができません。 -- 逆順で走査する場合、上書き問題はなく、状態遷移を正しく実行できます。 +- 順方向に走査すると、$dp[i, j]$ に到達した時点で、左上にある $dp[i-1, 1]$ ~ $dp[i-1, j-1]$ の値がすでに上書きされている可能性があり、正しい状態遷移結果を得られません。 +- 逆方向に走査すれば、上書きの問題は発生せず、状態遷移を正しく行えます。 -下の図は単一配列での $i = 1$ 行目から $i = 2$ 行目への遷移プロセスを示しています。通常順序走査と逆順走査の違いについて考えてみてください。 +次の図は、単一配列のもとで $i = 1$ 行目から $i = 2$ 行目へ変換する過程を示しています。順方向走査と逆方向走査の違いを考えてみてください。 === "<1>" - ![0-1ナップサックの空間最適化動的プログラミングプロセス](knapsack_problem.assets/knapsack_dp_comp_step1.png) + ![0-1 ナップサックの空間最適化後の動的計画法の過程](knapsack_problem.assets/knapsack_dp_comp_step1.png) === "<2>" ![knapsack_dp_comp_step2](knapsack_problem.assets/knapsack_dp_comp_step2.png) @@ -161,7 +161,7 @@ $$ === "<6>" ![knapsack_dp_comp_step6](knapsack_problem.assets/knapsack_dp_comp_step6.png) -コード実装では、配列 `dp` の最初の次元 $i$ を削除し、内側のループを逆走査に変更するだけです: +コード実装では、配列 `dp` の第1次元 $i$ をそのまま削除し、内側のループを逆方向走査に変更するだけで済みます。 ```src [file]{knapsack}-[class]{}-[func]{knapsack_dp_comp} diff --git a/ja/docs/chapter_dynamic_programming/summary.md b/ja/docs/chapter_dynamic_programming/summary.md index e3c6f858f..9f6ccb68b 100644 --- a/ja/docs/chapter_dynamic_programming/summary.md +++ b/ja/docs/chapter_dynamic_programming/summary.md @@ -1,23 +1,25 @@ # まとめ -- 動的プログラミングは問題を分解し、部分問題の解を保存することで冗長な計算を避け、計算効率を向上させます。 -- 時間を考慮しなければ、すべての動的プログラミング問題はバックトラッキング(力任せ探索)を使用して解決できますが、再帰木には多くの重複する部分問題があり、効率が非常に低くなります。記憶化リストを導入することで、計算されたすべての部分問題の解を保存し、重複する部分問題が一度だけ計算されることを保証できます。 -- 記憶化探索はトップダウンの再帰解法であり、動的プログラミングはボトムアップの反復アプローチに対応し、「表を埋める」ことに似ています。現在の状態は特定の局所状態のみに依存するため、dpテーブルの1次元を削除して空間計算量を削減できます。 -- 部分問題の分解は汎用的なアルゴリズムアプローチであり、分割統治法、動的プログラミング、バックトラッキングで特徴が異なります。 -- 動的プログラミング問題には3つの主要な特徴があります:重複する部分問題、最適部分構造、無記憶性。 -- 元の問題の最適解がその部分問題の最適解から構築できる場合、最適部分構造を持ちます。 -- 無記憶性とは、状態の将来の発展が現在の状態のみに依存し、過去に経験したすべての状態に依存しないことを意味します。多くの組み合わせ最適化問題にはこの特性がなく、動的プログラミングを使用して迅速に解決することはできません。 +### 要点の振り返り + +- 動的計画法は問題を分解し、部分問題の解を保存することで重複計算を避け、計算効率を高めます。 +- 時間を考慮しなければ、すべての動的計画法の問題はバックトラッキング(総当たり探索)で解けますが、再帰木には大量の重複部分問題が存在するため、効率はきわめて低くなります。メモ化配列を導入すると、計算済みのすべての部分問題の解を保存でき、重複部分問題が 1 回だけ計算されることを保証できます。 +- メモ化探索はトップダウンの再帰的解法であり、それに対応する動的計画法はボトムアップの漸化式による解法で、ちょうど「表を埋める」ようなものです。現在の状態は一部の局所状態にのみ依存するため、$dp$ 表の 1 次元を削減して空間計算量を下げることができます。 +- 部分問題への分解は汎用的なアルゴリズムの考え方であり、分割統治、動的計画法、バックトラッキングではそれぞれ異なる性質を持ちます。 +- 動的計画法の問題には 3 つの大きな特徴があります。重複部分問題、最適部分構造、無後効性です。 +- 元の問題の最適解が部分問題の最適解から構築できるなら、その問題は最適部分構造を持ちます。 +- 無後効性とは、ある状態の将来の発展がその状態のみに関係し、過去に経たすべての状態とは無関係であることを指します。多くの組合せ最適化問題は無後効性を持たず、動的計画法で高速に解くことはできません。 **ナップサック問題** -- ナップサック問題は最も典型的な動的プログラミング問題の1つで、0-1ナップサック、無制限ナップサック、複数ナップサックなどの変種があります。 -- 0-1ナップサックの状態定義は、最初の $i$ 個のアイテムを含む容量 $c$ のナップサックでの最大値です。アイテムをナップサックに入れないまたは入れるという決定に基づいて、最適部分構造を特定し、状態遷移方程式を構築できます。空間最適化では、各状態が直接上と左上の状態に依存するため、左上の状態の上書きを避けるためにリストを逆順で走査する必要があります。 -- 無制限ナップサック問題では、各種類のアイテムを選択できる数に制限がないため、アイテムを含める状態遷移は0-1ナップサックと異なります。状態が直接上と左の状態に依存するため、空間最適化では前方走査を含める必要があります。 -- コイン交換問題は無制限ナップサック問題の変種で、「最大」値を求めることから「最小」コイン数を求めることに変わり、状態遷移方程式は $\max()$ を $\min()$ に変更する必要があります。ナップサックの容量を「超えない」ことを追求することから、正確に目標金額を求めることに変わり、「目標金額を構成できない」無効解を表すために $amt + 1$ を使用します。 -- コイン交換問題IIは「最小コイン数」を求めることから「コインの組み合わせ数」を求めることに変わり、状態遷移方程式を $\min()$ から和算演算子に変更します。 +- ナップサック問題は最も典型的な動的計画法の問題の 1 つであり、0-1 ナップサック、完全ナップサック、多重ナップサックなどの派生があります。 +- 0-1 ナップサックの状態は、容量 $c$ のナップサックに対して、前 $i$ 個の品物で得られる最大価値として定義されます。ナップサックに入れない場合と入れる場合の 2 つの判断から最適部分構造を得て、状態遷移方程式を構築できます。空間最適化では、各状態が真上と左上の状態に依存するため、左上の状態が上書きされるのを避けるために配列を逆順に走査する必要があります。 +- 完全ナップサック問題では各品物の選択数に制限がないため、品物を入れる場合の状態遷移は 0-1 ナップサック問題とは異なります。状態は真上と真左の状態に依存するので、空間最適化では順方向に走査するべきです。 +- コイン両替問題は完全ナップサック問題の変種です。「最大」価値を求める問題から「最小」の硬貨枚数を求める問題へ変わるため、状態遷移方程式の $\max()$ は $\min()$ に置き換える必要があります。また、ナップサック容量を「超えない」ことを目指すのではなく、目標金額を「ちょうど」作ることを目指すため、$amt + 1$ を「目標金額を作れない」無効解の表現として用います。 +- コイン両替問題 II では、「最少硬貨枚数」を求める問題から「硬貨の組合せ数」を求める問題へ変わるため、状態遷移方程式も $\min()$ から総和演算子へ対応して変わります。 **編集距離問題** -- 編集距離(レーベンシュタイン距離)は2つの文字列間の類似度を測定し、一つの文字列を別の文字列に変更するために必要な最小編集ステップ数として定義され、編集操作には追加、削除、置換が含まれます。 -- 編集距離問題の状態定義は、$s$ の最初の $i$ 文字を $t$ の最初の $j$ 文字に変更するために必要な最小編集ステップ数です。$s[i] \ne t[j]$ の場合、追加、削除、置換の3つの決定があり、それぞれに対応する残余部分問題があります。これから最適部分構造を特定し、状態遷移方程式を構築できます。$s[i] = t[j]$ の場合、現在の文字の編集は必要ありません。 -- 編集距離では、状態が直接上、左、左上の状態に依存します。したがって、空間最適化後、前方走査も逆走査も正しく状態遷移を実行できません。これに対処するため、変数を使用して左上の状態を一時的に保存し、無制限ナップサック問題の状況と同等にし、空間最適化後に前方走査を可能にします。 +- 編集距離(Levenshtein 距離)は 2 つの文字列間の類似度を測るために用いられ、ある文字列を別の文字列へ変換するための最小編集回数として定義されます。編集操作には追加、削除、置換が含まれます。 +- 編集距離問題の状態は、$s$ の前 $i$ 文字を $t$ の前 $j$ 文字へ変更するのに必要な最小編集回数として定義されます。$s[i] \ne t[j]$ のときは、追加、削除、置換の 3 つの判断があり、それぞれに対応する残りの部分問題があります。これにより最適部分構造を見いだし、状態遷移方程式を構築できます。一方、$s[i] = t[j]$ のときは現在の文字を編集する必要はありません。 +- 編集距離では、状態は真上、真左、左上の状態に依存します。そのため、空間最適化後は順方向でも逆方向でも正しく状態遷移できません。そこで、変数を 1 つ用いて左上の状態を一時保存し、完全ナップサック問題と等価な形へ変換することで、空間最適化後に順方向走査を行えるようにします。 diff --git a/ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md b/ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md index c994c6baf..2b5b7b159 100644 --- a/ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md +++ b/ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md @@ -1,28 +1,28 @@ -# 無制限ナップサック問題 +# 完全ナップサック問題 -このセクションでは、まず別の一般的なナップサック問題である無制限ナップサックを解決し、次にその特殊ケースであるコイン交換問題を探索します。 +本節では、まずもう 1 つの代表的なナップサック問題である完全ナップサック問題を解き、その特殊例である硬貨交換問題について見ていきます。 -## 無制限ナップサック問題 +## 完全ナップサック問題 !!! question - $n$ 個のアイテムが与えられ、$i$ 番目のアイテムの重量は $wgt[i-1]$ で値は $val[i-1]$ です。容量が $cap$ のバックパックがあります。**各アイテムは複数回選択できます**。容量を超えることなくバックパックに入れることができるアイテムの最大値は何ですか?以下の例を参照してください。 + $n$ 個の品物が与えられ、$i$ 番目の品物の重さは $wgt[i-1]$、価値は $val[i-1]$ であり、容量 $cap$ のナップサックがあります。**各品物は繰り返し選択できます**。ナップサック容量の制約下で入れられる品物の最大価値を求めてください。例を以下の図に示します。 -![無制限ナップサック問題の例データ](unbounded_knapsack_problem.assets/unbounded_knapsack_example.png) +![完全ナップサック問題のサンプルデータ](unbounded_knapsack_problem.assets/unbounded_knapsack_example.png) -### 動的プログラミングアプローチ +### 動的計画法の考え方 -無制限ナップサック問題は0-1ナップサック問題と非常に似ており、**唯一の違いはアイテムを選択できる回数に制限がないことです**。 +完全ナップサック問題は 0-1 ナップサック問題と非常によく似ています。**違いは、品物の選択回数に制限がない点だけです**。 -- 0-1ナップサック問題では、各アイテムは1つしかないため、アイテム $i$ をバックパックに入れた後は、前の $i-1$ 個のアイテムからのみ選択できます。 -- 無制限ナップサック問題では、各アイテムの数量は無制限であるため、アイテム $i$ をバックパックに入れた後も、**前の $i$ 個のアイテムから引き続き選択できます**。 +- 0-1 ナップサック問題では、各品物は 1 つしかないため、品物 $i$ をナップサックに入れた後は先頭 $i-1$ 個の品物からしか選べません。 +- 完全ナップサック問題では、各品物の数は無限であるため、品物 $i$ をナップサックに入れた後も、**引き続き先頭 $i$ 個の品物から選べます**。 -無制限ナップサック問題のルールの下で、状態 $[i, c]$ は2つの方法で変化できます。 +完全ナップサック問題では、状態 $[i, c]$ の変化は 2 つの場合に分けられます。 -- **アイテム $i$ を入れない**:0-1ナップサック問題と同様に、$[i-1, c]$ に遷移します。 -- **アイテム $i$ を入れる**:0-1ナップサック問題とは異なり、$[i, c-wgt[i-1]]$ に遷移します。 +- **品物 $i$ を入れない** :0-1 ナップサック問題と同様に、$[i-1, c]$ へ遷移します。 +- **品物 $i$ を入れる** :0-1 ナップサック問題とは異なり、$[i, c-wgt[i-1]]$ へ遷移します。 -したがって、状態遷移方程式は次のようになります: +したがって、状態遷移方程式は次のようになります。 $$ dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1]) @@ -30,7 +30,7 @@ $$ ### コード実装 -2つの問題のコードを比較すると、状態遷移が $i-1$ から $i$ に変わり、残りは完全に同一です: +2 つの問題のコードを比較すると、状態遷移の中で 1 か所だけ $i-1$ が $i$ に変わり、それ以外は完全に同じです。 ```src [file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp} @@ -38,12 +38,12 @@ $$ ### 空間最適化 -現在の状態は左と上の状態から来るため、**空間最適化解法は $dp$ テーブルの各行に対して前方走査を実行する必要があります**。 +現在の状態は左側と上側の状態から遷移してくるため、**空間最適化後は $dp$ テーブルの各行を順方向に走査する必要があります**。 -この走査順序は0-1ナップサックの場合とは逆です。違いを理解するために下の図を参照してください。 +この走査順序は 0-1 ナップサックとはちょうど逆です。両者の違いは次の図を用いて理解してください。 === "<1>" - ![空間最適化後の無制限ナップサック問題の動的プログラミングプロセス](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png) + ![完全ナップサック問題における空間最適化後の動的計画法の過程](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png) === "<2>" ![unbounded_knapsack_dp_comp_step2](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png) @@ -60,67 +60,67 @@ $$ === "<6>" ![unbounded_knapsack_dp_comp_step6](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png) -コード実装は非常に簡単で、配列 `dp` の最初の次元を削除するだけです: +コード実装は比較的簡単で、配列 `dp` の第 1 次元を削除するだけです。 ```src [file]{unbounded_knapsack}-[class]{}-[func]{unbounded_knapsack_dp_comp} ``` -## コイン交換問題 +## 硬貨交換問題 -ナップサック問題は動的プログラミング問題の大きなクラスの代表であり、コイン交換問題など多くの変種があります。 +ナップサック問題は動的計画法の代表的な問題群であり、多くの派生問題があります。硬貨交換問題もその 1 つです。 !!! question - $n$ 種類のコインが与えられ、$i$ 番目の種類のコインの額面は $coins[i - 1]$ で、目標金額は $amt$ です。**各種類のコインは複数回選択できます**。目標金額を構成するのに必要な最小コイン数は何ですか?目標金額を構成できない場合は $-1$ を返してください。以下の例を参照してください。 + $n$ 種類の硬貨が与えられ、$i$ 番目の硬貨の額面は $coins[i - 1]$ 、目標金額は $amt$ です。**各硬貨は繰り返し選択できます**。目標金額を作るために必要な最小の硬貨枚数を求めてください。目標金額を作れない場合は $-1$ を返します。例を以下の図に示します。 -![コイン交換問題の例データ](unbounded_knapsack_problem.assets/coin_change_example.png) +![硬貨交換問題のサンプルデータ](unbounded_knapsack_problem.assets/coin_change_example.png) -### 動的プログラミングアプローチ +### 動的計画法の考え方 -**コイン交換は無制限ナップサック問題の特殊ケースと見なすことができ**、以下の類似点と相違点を共有しています。 +**硬貨交換は完全ナップサック問題の特殊なケースとみなせます**。両者には次の対応関係と相違点があります。 -- 2つの問題は互いに変換できます:「アイテム」は「コイン」に対応し、「アイテムの重量」は「コインの額面」に対応し、「バックパックの容量」は「目標金額」に対応します。 -- 最適化目標は逆です:無制限ナップサック問題はアイテムの値を最大化することを目的とし、コイン交換問題はコインの数を最小化することを目的とします。 -- 無制限ナップサック問題はバックパック容量を「超えない」解を求め、コイン交換は目標金額を「正確に」構成する解を求めます。 +- 2 つの問題は相互に変換でき、「品物」は「硬貨」、「品物の重さ」は「硬貨の額面」、「ナップサック容量」は「目標金額」に対応します。 +- 最適化の目標は逆であり、完全ナップサック問題は品物価値の最大化、硬貨交換問題は硬貨枚数の最小化を目指します。 +- 完全ナップサック問題はナップサック容量を「超えない」解を求めますが、硬貨交換は目標金額に「ちょうど」一致する解を求めます。 -**第1ステップ:各ラウンドの意思決定を考え、状態を定義し、それにより $dp$ テーブルを導出する** +**ステップ 1:各ラウンドの選択を考え、状態を定義して、$dp$ テーブルを得る** -状態 $[i, a]$ は部分問題に対応します:**最初の $i$ 種類のコインを使用して金額 $a$ を構成できる最小コイン数**、$dp[i, a]$ と表記されます。 +状態 $[i, a]$ に対応する部分問題は、**先頭 $i$ 種類の硬貨で金額 $a$ を作るための最小硬貨枚数**であり、これを $dp[i, a]$ と表します。 -二次元 $dp$ テーブルのサイズは $(n+1) \times (amt+1)$ です。 +2 次元 $dp$ テーブルのサイズは $(n+1) \times (amt+1)$ です。 -**第2ステップ:最適部分構造を特定し、状態遷移方程式を導出する** +**ステップ 2:最適部分構造を見つけ、状態遷移方程式を導く** -この問題は状態遷移方程式の2つの側面で無制限ナップサック問題と異なります。 +本問の状態遷移方程式は、完全ナップサック問題と比べて次の 2 点が異なります。 -- この問題は最小値を求めるため、演算子 $\max()$ を $\min()$ に変更する必要があります。 -- 最適化はコインの数に焦点を当てているため、コインが選択されたときに単純に $+1$ を追加します。 +- 本問では最小値を求めるため、演算子 $\max()$ を $\min()$ に変更する必要があります。 +- 最適化の対象は品物価値ではなく硬貨枚数であるため、硬貨を選んだときは $+1$ すれば十分です。 $$ dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1) $$ -**第3ステップ:境界条件と状態遷移順序を定義する** +**ステップ 3:境界条件と状態遷移順序を決める** -目標金額が $0$ の場合、それを構成するのに必要な最小コイン数は $0$ であるため、最初の列のすべての $dp[i, 0]$ は $0$ です。 +目標金額が $0$ のとき、それを作るための最小硬貨枚数は $0$ です。つまり、先頭列のすべての $dp[i, 0]$ は $0$ になります。 -コインがない場合、**任意の金額 >0 を構成することは不可能**であり、これは無効な解です。状態遷移方程式の $\min()$ 関数が無効な解を認識してフィルタリングできるように、$+\infty$ を使用してそれらを表現することを検討し、つまり最初の行のすべての $dp[0, a]$ を $+\infty$ に設定します。 +硬貨が 1 枚もない場合、**任意の $> 0$ の目標金額を作ることはできません**。これは無効解です。状態遷移方程式内の $\min()$ 関数が無効解を識別して除外できるように、それらを $+ \infty$ で表すことを考えます。すなわち、先頭行のすべての $dp[0, a]$ を $+ \infty$ とします。 ### コード実装 -ほとんどのプログラミング言語は $+\infty$ 変数を提供しておらず、整数 `int` の最大値のみを代替として使用できます。これによりオーバーフローが発生する可能性があります:状態遷移方程式の $+1$ 演算がオーバーフローする可能性があります。 +多くのプログラミング言語には $+ \infty$ を表す変数が用意されていないため、通常は整数型 `int` の最大値で代用します。しかし、その場合は大きな数のオーバーフローが起こり得ます。状態遷移方程式中の $+ 1$ 操作で桁あふれが発生する可能性があるためです。 -この理由で、数値 $amt + 1$ を使用して無効な解を表します。なぜなら、$amt$ を構成するのに必要な最大コイン数は最大でも $amt$ だからです。結果を返す前に、$dp[n, amt]$ が $amt + 1$ に等しいかどうかを確認し、そうであれば $-1$ を返し、目標金額を構成できないことを示します。コードは以下の通りです: +そのため、ここでは数値 $amt + 1$ を無効解の表現として用います。金額 $amt$ を作るための硬貨枚数は最大でも $amt$ 枚だからです。最後に返す前に、$dp[n, amt]$ が $amt + 1$ に等しいかを判定し、等しければ $-1$ を返して目標金額を作れないことを表します。コードは次のとおりです。 ```src [file]{coin_change}-[class]{}-[func]{coin_change_dp} ``` -下の図はコイン交換問題の動的プログラミングプロセスを示しており、無制限ナップサック問題と非常に似ています。 +次の図は硬貨交換の動的計画法の過程を示しており、完全ナップサック問題と非常によく似ています。 === "<1>" - ![コイン交換問題の動的プログラミングプロセス](unbounded_knapsack_problem.assets/coin_change_dp_step1.png) + ![硬貨交換問題の動的計画法の過程](unbounded_knapsack_problem.assets/coin_change_dp_step1.png) === "<2>" ![coin_change_dp_step2](unbounded_knapsack_problem.assets/coin_change_dp_step2.png) @@ -166,31 +166,31 @@ $$ ### 空間最適化 -コイン交換問題の空間最適化は無制限ナップサック問題と同じ方法で処理されます: +硬貨交換の空間最適化の方法は、完全ナップサック問題と同じです。 ```src [file]{coin_change}-[class]{}-[func]{coin_change_dp_comp} ``` -## コイン交換問題II +## 硬貨交換問題 II !!! question - $n$ 種類のコインが与えられ、$i$ 番目の種類のコインの額面は $coins[i - 1]$ で、目標金額は $amt$ です。各種類のコインは複数回選択でき、**目標金額を構成できるコインの組み合わせは何通りありますか**。以下の例を参照してください。 + $n$ 種類の硬貨が与えられ、$i$ 番目の硬貨の額面は $coins[i - 1]$ 、目標金額は $amt$ です。各硬貨は繰り返し選択できるとして、**目標金額を作る硬貨の組合せ数**を求めてください。例を以下の図に示します。 -![コイン交換問題IIの例データ](unbounded_knapsack_problem.assets/coin_change_ii_example.png) +![硬貨交換問題 II のサンプルデータ](unbounded_knapsack_problem.assets/coin_change_ii_example.png) -### 動的プログラミングアプローチ +### 動的計画法の考え方 -前の問題と比較して、この問題の目標は組み合わせの数を決定することであるため、部分問題は次のようになります:**最初の $i$ 種類のコインを使用して金額 $a$ を構成できる組み合わせの数**。$dp$ テーブルはサイズ $(n+1) \times (amt + 1)$ の二次元行列のまま残ります。 +前問と比べて、本問の目的は組合せ数を求めることです。そのため、部分問題は **先頭 $i$ 種類の硬貨で金額 $a$ を作れる組合せ数** になります。一方、$dp$ テーブルは引き続きサイズ $(n+1) \times (amt + 1)$ の 2 次元行列です。 -現在の状態の組み合わせ数は、現在のコインを選択しない組み合わせと現在のコインを選択する組み合わせの合計です。状態遷移方程式は: +現在の状態における組合せ数は、現在の硬貨を選ばない場合と選ぶ場合の 2 つの選択肢の組合せ数の和に等しくなります。状態遷移方程式は次のとおりです。 $$ dp[i, a] = dp[i-1, a] + dp[i, a - coins[i-1]] $$ -目標金額が $0$ の場合、目標金額を構成するのにコインは必要ないため、最初の列のすべての $dp[i, 0]$ は $1$ に初期化されるべきです。コインがない場合、任意の金額 >0 を構成することは不可能であるため、最初の行のすべての $dp[0, a]$ は $0$ に設定されるべきです。 +目標金額が $0$ のときは、どの硬貨も選ばなくても目標金額を作れるため、先頭列のすべての $dp[i, 0]$ を $1$ に初期化します。硬貨がないときは、任意の $>0$ の目標金額を作れないため、先頭行のすべての $dp[0, a]$ は $0$ になります。 ### コード実装 @@ -200,7 +200,7 @@ $$ ### 空間最適化 -空間最適化アプローチは同じで、コインの次元を削除するだけです: +空間最適化の方法も同様で、硬貨の次元を削除するだけです。 ```src [file]{coin_change_ii}-[class]{}-[func]{coin_change_ii_dp_comp} diff --git a/ja/docs/chapter_graph/graph.md b/ja/docs/chapter_graph/graph.md index c0b580a4c..21b1986b7 100644 --- a/ja/docs/chapter_graph/graph.md +++ b/ja/docs/chapter_graph/graph.md @@ -1,6 +1,6 @@ # グラフ -グラフは非線形データ構造の一種で、頂点で構成されます。グラフ$G$は、頂点の集合$V$と辺の集合$E$の組み合わせとして抽象的に表現できます。以下の例は、5つの頂点と7つの辺を含むグラフを示しています。 +グラフ(graph)は、頂点(vertex)辺(edge)から構成される非線形データ構造です。グラフ $G$ は、頂点集合 $V$ と辺集合 $E$ からなる集合として抽象的に表せます。以下の例は、5 個の頂点と 7 本の辺を含むグラフを示しています。 $$ \begin{aligned} @@ -10,74 +10,74 @@ G & = \{ V, E \} \newline \end{aligned} $$ -頂点をノード、辺をノードを接続する参照(ポインタ)と見なすと、グラフは連結リストから拡張されたデータ構造として見ることができます。下図に示すように、**線形関係(連結リスト)や分割統治関係(木)と比較して、ネットワーク関係(グラフ)は自由度が高いため、より複雑です**。 +頂点をノード、辺を各ノードをつなぐ参照(ポインタ)とみなせば、グラフは連結リストを拡張したデータ構造の一種と捉えられます。次の図に示すように、**線形関係(連結リスト)や分治関係(木)と比べて、ネットワーク関係(グラフ)は自由度が高く**、そのぶん複雑です。 ![連結リスト、木、グラフの関係](graph.assets/linkedlist_tree_graph.png) ## グラフの一般的な種類と用語 -グラフは、辺に方向があるかどうかによって無向グラフ有向グラフに分けることができます(下図参照)。 +辺が方向性を持つかどうかに応じて、無向グラフ(undirected graph)有向グラフ(directed graph)に分けられます。次の図のとおりです。 -- 無向グラフでは、辺は2つの頂点間の「双方向」接続を表します。例えば、Facebookの「友達」関係です。 -- 有向グラフでは、辺に方向性があります。つまり、辺$A \rightarrow B$と$A \leftarrow B$は互いに独立しています。例えば、InstagramやTikTokの「フォロー」と「フォロワー」の関係です。 +- 無向グラフでは、辺は 2 つの頂点間の「双方向」の接続関係を表します。例えば WeChat や QQ における「友だち関係」です。 +- 有向グラフでは、辺は方向性を持ち、すなわち $A \rightarrow B$ と $A \leftarrow B$ の 2 方向の辺は互いに独立です。例えば Weibo や Douyin における「フォロー」と「フォロワー」の関係です。 ![有向グラフと無向グラフ](graph.assets/directed_graph.png) -すべての頂点が接続されているかどうかによって、グラフは連結グラフ非連結グラフに分けることができます(下図参照)。 +すべての頂点が連結しているかどうかに応じて、連結グラフ(connected graph)非連結グラフ(disconnected graph)に分けられます。次の図のとおりです。 -- 連結グラフでは、任意の頂点から開始して他の任意の頂点に到達することが可能です。 -- 非連結グラフでは、任意の開始頂点から到達できない頂点が少なくとも1つ存在します。 +- 連結グラフでは、ある頂点から出発すると、ほかの任意の頂点に到達できます。 +- 非連結グラフでは、ある頂点から出発すると、少なくとも 1 つの頂点には到達できません。 ![連結グラフと非連結グラフ](graph.assets/connected_graph.png) -辺に重み変数を追加することもでき、その結果として重み付きグラフが生まれます(下図参照)。例えば、Instagramでは、システムがあなたと他のユーザーとの間の相互作用レベル(いいね、閲覧、コメントなど)によってフォロワーとフォロー中のリストをソートします。このような相互作用ネットワークは重み付きグラフで表現できます。 +辺に「重み」の変数を追加すると、次の図に示すような重み付きグラフ(weighted graph)が得られます。例えば『Honor of Kings』のようなモバイルゲームでは、システムが共にプレイした時間に基づいてプレイヤー間の「親密度」を計算します。この親密度ネットワークは重み付きグラフで表せます。 ![重み付きグラフと重みなしグラフ](graph.assets/weighted_graph.png) -グラフデータ構造には、以下のような一般的に使用される用語があります。 +グラフというデータ構造には、次のような基本用語があります。 -- 隣接:2つの頂点を接続する辺がある場合、これら2つの頂点は「隣接」していると言われます。上図では、頂点1の隣接頂点は頂点2、3、5です。 -- パス:頂点Aから頂点Bまでに通過する辺のシーケンスを、AからBへのパスと呼びます。上図では、辺のシーケンス1-5-2-4は頂点1から頂点4へのパスです。 -- 次数:頂点が持つ辺の数です。有向グラフの場合、入次数はその頂点を指す辺の数、出次数はその頂点から出る辺の数を指します。 +- 隣接(adjacency):2 つの頂点の間に辺が存在するとき、この 2 つの頂点は「隣接している」といいます。上図では、頂点 1 に隣接する頂点は 2、3、5 です。 +- 経路(path):頂点 A から頂点 B までに通過する辺で構成された列を、A から B への「経路」と呼びます。上図では、辺の列 1-5-2-4 は頂点 1 から頂点 4 への 1 本の経路です。 +- 次数(degree):ある頂点が持つ辺の本数です。有向グラフでは、入次数(in-degree)はその頂点に向かう辺の本数を表し、出次数(out-degree)はその頂点から出る辺の本数を表します。 ## グラフの表現 -グラフの一般的な表現には「隣接行列」と「隣接リスト」があります。以下の例では無向グラフを使用します。 +グラフの一般的な表現方法には「隣接行列」と「隣接リスト」があります。以下では無向グラフを例に説明します。 ### 隣接行列 -グラフの頂点数を$n$とすると、隣接行列は$n \times n$の行列を使用してグラフを表現します。各行(列)は頂点を表し、行列要素は辺を表し、2つの頂点間に辺があるかどうかを$1$または$0$で示します。 +グラフの頂点数を $n$ とすると、隣接行列(adjacency matrix)は $n \times n$ の行列を用いてグラフを表します。各行(列)は 1 つの頂点を表し、行列要素は辺を表します。$1$ または $0$ を用いて、2 つの頂点の間に辺があるかどうかを示します。 -下図に示すように、隣接行列を$M$、頂点のリストを$V$とすると、行列要素$M[i, j] = 1$は頂点$V[i]$と頂点$V[j]$の間に辺があることを示し、逆に$M[i, j] = 0$は2つの頂点間に辺がないことを示します。 +次の図のように、隣接行列を $M$、頂点リストを $V$ とすると、行列要素 $M[i, j] = 1$ は頂点 $V[i]$ から頂点 $V[j]$ への辺が存在することを表し、逆に $M[i, j] = 0$ は 2 つの頂点の間に辺がないことを表します。 -![隣接行列によるグラフの表現](graph.assets/adjacency_matrix.png) +![グラフの隣接行列による表現](graph.assets/adjacency_matrix.png) -隣接行列には以下の特性があります。 +隣接行列には次の特徴があります。 -- 頂点は自分自身に接続することはできないため、隣接行列の主対角線上の要素は意味がありません。 -- 無向グラフの場合、両方向の辺は等価であるため、隣接行列は主対角線に関して対称です。 -- 隣接行列の要素を$1$と$0$から重みに置き換えることで、重み付きグラフを表現できます。 +- 単純グラフでは、頂点は自分自身とは接続できないため、このとき隣接行列の主対角線上の要素には意味がありません。 +- 無向グラフでは、2 方向の辺は等価であるため、このとき隣接行列は主対角線に関して対称です。 +- 隣接行列の要素を $1$ と $0$ から重みに置き換えると、重み付きグラフを表せます。 -隣接行列でグラフを表現する場合、行列要素に直接アクセスして辺を取得できるため、追加、削除、検索、変更の操作が効率的で、すべて時間計算量$O(1)$です。ただし、行列の空間計算量は$O(n^2)$で、より多くのメモリを消費します。 +隣接行列でグラフを表す場合、行列要素に直接アクセスして辺を取得できるため、追加・削除・検索・更新の操作効率は高く、時間計算量はいずれも $O(1)$ です。しかし、行列の空間計算量は $O(n^2)$ であり、メモリ使用量は多くなります。 ### 隣接リスト -隣接リストは$n$個の連結リストを使用してグラフを表現し、各連結リストノードは頂点を表します。$i$番目の連結リストは頂点$i$に対応し、すべての隣接頂点(その頂点に接続された頂点)を含みます。下図は隣接リストを使用して格納されたグラフの例を示しています。 +隣接リスト(adjacency list)は、$n$ 本の連結リストを使ってグラフを表します。連結リストのノードは頂点を表します。第 $i$ 本の連結リストは頂点 $i$ に対応し、その頂点に隣接するすべての頂点(その頂点と接続された頂点)を格納します。次の図は、隣接リストで保存したグラフの例です。 -![隣接リストによるグラフの表現](graph.assets/adjacency_list.png) +![グラフの隣接リストによる表現](graph.assets/adjacency_list.png) -隣接リストは実際の辺のみを格納し、辺の総数は$n^2$よりもはるかに少ないことが多く、より空間効率的です。ただし、隣接リストで辺を見つけるには連結リストを走査する必要があるため、その時間効率は隣接行列ほど良くありません。 +隣接リストは実際に存在する辺だけを格納し、辺の総数は通常 $n^2$ よりはるかに小さいため、より省スペースです。しかし、隣接リストでは辺を見つけるために連結リストを走査する必要があるため、時間効率は隣接行列に及びません。 -上図を観察すると、**隣接リストの構造はハッシュテーブルの「チェイン法」と非常に似ているため、同様の方法を使用して効率を最適化できます**。例えば、連結リストが長い場合、それをAVL木や赤黒木に変換して、時間効率を$O(n)$から$O(\log n)$に最適化できます。連結リストをハッシュテーブルに変換することで、時間計算量を$O(1)$に削減することもできます。 +上図を見ると、**隣接リストの構造はハッシュテーブルにおける「連鎖アドレス法」と非常によく似ているため、同様の方法で効率を最適化できます**。例えば、連結リストが長い場合は AVL 木や赤黒木に変換して時間効率を $O(n)$ から $O(\log n)$ に改善できます。さらに、連結リストをハッシュテーブルに変換すれば、時間計算量を $O(1)$ まで下げられます。 ## グラフの一般的な応用 -下表に示すように、多くの現実世界のシステムはグラフでモデル化でき、対応する問題はグラフ計算問題に削減できます。 +次の表のように、多くの現実のシステムはグラフでモデル化でき、対応する問題もグラフ計算の問題に帰着できます。 -

  現実生活の一般的なグラフ

+

  現実世界でよく見られるグラフ

-| | 頂点 | 辺 | グラフ計算問題 | -| -------------- | -------------- | -------------------------------- | --------------------------- | -| ソーシャルネットワーク | ユーザー | フォロー / フォロワー関係 | 潜在的フォロー推薦 | -| 地下鉄路線 | 駅 | 駅間の接続性 | 最短ルート推薦 | -| 太陽系 | 天体 | 天体間の重力 | 惑星軌道計算 | +| | 頂点 | 辺 | グラフ計算問題 | +| -------- | ---- | -------------------- | ------------ | +| ソーシャルネットワーク | ユーザー | 友だち関係 | 潜在的な友だちの推薦 | +| 地下鉄路線 | 駅 | 駅間の接続性 | 最短経路の推薦 | +| 太陽系 | 天体 | 天体間の万有引力作用 | 惑星軌道の計算 | diff --git a/ja/docs/chapter_graph/graph_operations.md b/ja/docs/chapter_graph/graph_operations.md index 0a86bd56f..070081a37 100644 --- a/ja/docs/chapter_graph/graph_operations.md +++ b/ja/docs/chapter_graph/graph_operations.md @@ -1,18 +1,18 @@ # グラフの基本操作 -グラフの基本操作は「辺」に対する操作と「頂点」に対する操作に分けることができます。「隣接行列」と「隣接リスト」の2つの表現方法の下では、実装が異なります。 +グラフの基本操作は、「辺」に対する操作と「頂点」に対する操作に分けられます。「隣接行列」と「隣接リスト」の 2 つの表現方法では、実装方法が異なります。 ## 隣接行列に基づく実装 -$n$個の頂点を持つ無向グラフが与えられた場合、さまざまな操作は下図のように実装されます。 +頂点数が $n$ の無向グラフを与えると、各種操作の実装方法は次図のとおりです。 -- **辺の追加または削除**:隣接行列内の指定された辺を直接変更し、$O(1)$時間を使用します。無向グラフであるため、両方向の辺を同時に更新する必要があります。 -- **頂点の追加**:隣接行列の末尾に行と列を追加し、すべて$0$で埋めます。$O(n)$時間を使用します。 -- **頂点の削除**:隣接行列内の行と列を削除します。最悪の場合は最初の行と列が削除されるときで、$(n-1)^2$個の要素を「上と左に移動」する必要があり、$O(n^2)$時間を使用します。 -- **初期化**:$n$個の頂点を渡し、長さ$n$の頂点リスト`vertices`を初期化し、$O(n)$時間を使用します。$n \times n$サイズの隣接行列`adjMat`を初期化し、$O(n^2)$時間を使用します。 +- **辺の追加または削除**:隣接行列で指定した辺を直接変更すればよく、$O(1)$ 時間です。無向グラフであるため、2 方向の辺を同時に更新する必要があります。 +- **頂点の追加**:隣接行列の末尾に 1 行 1 列を追加し、すべてを $0$ で埋めればよく、$O(n)$ 時間です。 +- **頂点の削除**:隣接行列から 1 行 1 列を削除します。先頭行と先頭列を削除する場合が最悪で、$(n-1)^2$ 個の要素を「左上へ移動」させる必要があるため、$O(n^2)$ 時間です。 +- **初期化**:$n$ 個の頂点を受け取り、長さ $n$ の頂点リスト `vertices` を初期化するのに $O(n)$ 時間、サイズ $n \times n$ の隣接行列 `adjMat` を初期化するのに $O(n^2)$ 時間かかります。 === "隣接行列の初期化" - ![隣接行列での初期化、辺の追加と削除、頂点の追加と削除](graph_operations.assets/adjacency_matrix_step1_initialization.png) + ![隣接行列の初期化、辺の追加と削除、頂点の追加と削除](graph_operations.assets/adjacency_matrix_step1_initialization.png) === "辺の追加" ![adjacency_matrix_add_edge](graph_operations.assets/adjacency_matrix_step2_add_edge.png) @@ -26,7 +26,7 @@ $n$個の頂点を持つ無向グラフが与えられた場合、さまざま === "頂点の削除" ![adjacency_matrix_remove_vertex](graph_operations.assets/adjacency_matrix_step5_remove_vertex.png) -以下は隣接行列を使用して表現されたグラフの実装コードです: +以下は、隣接行列でグラフを表した実装コードです: ```src [file]{graph_adjacency_matrix}-[class]{graph_adj_mat}-[func]{} @@ -34,16 +34,16 @@ $n$個の頂点を持つ無向グラフが与えられた場合、さまざま ## 隣接リストに基づく実装 -総計$n$個の頂点と$m$個の辺を持つ無向グラフが与えられた場合、さまざまな操作は下図のように実装できます。 +無向グラフの頂点総数を $n$、辺総数を $m$ とすると、各種操作は次図の方法で実装できます。 -- **辺の追加**:対応する頂点の連結リストの末尾に辺を追加するだけで、$O(1)$時間を使用します。無向グラフであるため、両方向に同時に辺を追加する必要があります。 -- **辺の削除**:対応する頂点の連結リスト内で指定された辺を見つけて削除し、$O(m)$時間を使用します。無向グラフでは、両方向の辺を同時に削除する必要があります。 -- **頂点の追加**:隣接リストに連結リストを追加し、新しい頂点をリストのヘッドノードにし、$O(1)$時間を使用します。 -- **頂点の削除**:隣接リスト全体を走査し、指定された頂点を含むすべての辺を削除する必要があり、$O(n + m)$時間を使用します。 -- **初期化**:隣接リストに$n$個の頂点と$2m$個の辺を作成し、$O(n + m)$時間を使用します。 +- **辺の追加**:頂点に対応する連結リストの末尾に辺を追加すればよく、$O(1)$ 時間です。無向グラフなので、2 方向の辺を同時に追加する必要があります。 +- **辺の削除**:頂点に対応する連結リストから指定した辺を探して削除するため、$O(m)$ 時間です。無向グラフでは、2 方向の辺を同時に削除する必要があります。 +- **頂点の追加**:隣接リストに 1 つの連結リストを追加し、新しい頂点をその連結リストの先頭ノードとするため、$O(1)$ 時間です。 +- **頂点の削除**:隣接リスト全体を走査し、指定した頂点を含むすべての辺を削除する必要があるため、$O(n + m)$ 時間です。 +- **初期化**:隣接リストに $n$ 個の頂点と $2m$ 本の辺を作成するため、$O(n + m)$ 時間です。 === "隣接リストの初期化" - ![隣接リストでの初期化、辺の追加と削除、頂点の追加と削除](graph_operations.assets/adjacency_list_step1_initialization.png) + ![隣接リストの初期化、辺の追加と削除、頂点の追加と削除](graph_operations.assets/adjacency_list_step1_initialization.png) === "辺の追加" ![adjacency_list_add_edge](graph_operations.assets/adjacency_list_step2_add_edge.png) @@ -57,12 +57,12 @@ $n$個の頂点を持つ無向グラフが与えられた場合、さまざま === "頂点の削除" ![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_step5_remove_vertex.png) -以下は隣接リストのコード実装です。上図と比較して、実際のコードには以下の違いがあります。 +以下は隣接リストのコード実装です。上図と比べると、実際のコードには次の違いがあります。 -- 頂点の追加と削除の便宜、およびコードの簡素化のため、連結リストの代わりにリスト(動的配列)を使用します。 -- ハッシュテーブルを使用して隣接リストを格納し、`key`が頂点インスタンス、`value`がその頂点の隣接頂点のリスト(連結リスト)です。 +- 頂点の追加と削除を容易にし、コードを簡潔にするため、連結リストの代わりにリスト(動的配列)を使用しています。 +- ハッシュテーブルを用いて隣接リストを格納しており、`key` は頂点インスタンス、`value` はその頂点に隣接する頂点のリスト(連結リスト)です。 -さらに、隣接リストで頂点を表現するために`Vertex`クラスを使用します。その理由は:隣接行列のようにリストインデックスを使用して異なる頂点を区別する場合、インデックス$i$の頂点を削除したい場合、隣接リスト全体を走査し、$i$より大きいすべてのインデックスを1つずつ減少させる必要があり、これは非常に非効率的です。しかし、各頂点が一意の`Vertex`インスタンスである場合、頂点を削除しても他の頂点に変更を加える必要がありません。 +また、隣接リストでは頂点を表すために `Vertex` クラスを使用しています。その理由は、もし隣接行列と同様にリストのインデックスで異なる頂点を区別すると、インデックス $i$ の頂点を削除する場合、隣接リスト全体を走査して、$i$ より大きいすべてのインデックスを $1$ 減らす必要があり、効率が非常に低いためです。これに対して、各頂点が一意な `Vertex` インスタンスであれば、ある頂点を削除しても他の頂点を変更する必要はありません。 ```src [file]{graph_adjacency_list}-[class]{graph_adj_list}-[func]{} @@ -70,17 +70,17 @@ $n$個の頂点を持つ無向グラフが与えられた場合、さまざま ## 効率の比較 -グラフに$n$個の頂点と$m$個の辺があると仮定すると、下表は隣接行列と隣接リストの時間効率と空間効率を比較しています。 +グラフに $n$ 個の頂点と $m$ 本の辺があるとすると、次の表は隣接行列と隣接リストの時間効率および空間効率を比較したものです。なお、隣接リスト(連結リスト)は本記事の実装に対応し、隣接リスト(ハッシュテーブル)はすべての連結リストをハッシュテーブルに置き換えた実装を指します。

  隣接行列と隣接リストの比較

-| | 隣接行列 | 隣接リスト(連結リスト) | 隣接リスト(ハッシュテーブル) | -| ---------------- | -------------- | ----------------------- | ----------------------------- | -| 隣接性の判定 | $O(1)$ | $O(m)$ | $O(1)$ | -| 辺の追加 | $O(1)$ | $O(1)$ | $O(1)$ | -| 辺の削除 | $O(1)$ | $O(m)$ | $O(1)$ | -| 頂点の追加 | $O(n)$ | $O(1)$ | $O(1)$ | -| 頂点の削除 | $O(n^2)$ | $O(n + m)$ | $O(n)$ | -| メモリ空間使用量 | $O(n^2)$ | $O(n + m)$ | $O(n + m)$ | +| | 隣接行列 | 隣接リスト(連結リスト) | 隣接リスト(ハッシュテーブル) | +| ------------ | -------- | -------------- | ---------------- | +| 隣接判定 | $O(1)$ | $O(n)$ | $O(1)$ | +| 辺の追加 | $O(1)$ | $O(1)$ | $O(1)$ | +| 辺の削除 | $O(1)$ | $O(n)$ | $O(1)$ | +| 頂点の追加 | $O(n)$ | $O(1)$ | $O(1)$ | +| 頂点の削除 | $O(n^2)$ | $O(n + m)$ | $O(n)$ | +| メモリ使用量 | $O(n^2)$ | $O(n + m)$ | $O(n + m)$ | -上表を観察すると、隣接リスト(ハッシュテーブル)が最高の時間効率と空間効率を持っているように見えます。しかし、実際には、隣接行列での辺に対する操作がより効率的で、単一の配列アクセスまたは代入操作のみが必要です。全体的に、隣接行列は「空間と時間のトレードオフ」の原則を例示し、隣接リストは「時間と空間のトレードオフ」を例示しています。 +上表を見ると、隣接リスト(ハッシュテーブル)の時間効率と空間効率が最も優れているように見えます。しかし実際には、隣接行列のほうが辺の操作効率は高く、必要なのは 1 回の配列アクセスまたは代入だけです。総合的に見ると、隣接行列は「空間を時間と引き換えにする」原則を体現し、隣接リストは「時間を空間と引き換えにする」原則を体現しています。 diff --git a/ja/docs/chapter_graph/graph_traversal.md b/ja/docs/chapter_graph/graph_traversal.md index 11e61b578..c9e7242f7 100644 --- a/ja/docs/chapter_graph/graph_traversal.md +++ b/ja/docs/chapter_graph/graph_traversal.md @@ -1,33 +1,37 @@ -# グラフ走査 +# グラフの走査 -木は「一対多」の関係を表現し、グラフはより高い自由度を持ち、任意の「多対多」の関係を表現できます。したがって、木をグラフの特別なケースと見なすことができます。明らかに、**木の走査操作もグラフ走査操作の特別なケースです**。 +木は「一対多」の関係を表すのに対し、グラフはより高い自由度を持ち、任意の「多対多」の関係を表現できます。したがって、木はグラフの一種の特殊な場合とみなせます。明らかに、**木の走査操作もグラフの走査操作の一種の特殊な場合です**。 -グラフと木の両方で、走査操作を実装するために探索アルゴリズムの応用が必要です。グラフ走査は2つのタイプに分けることができます:幅優先探索(BFS)深さ優先探索(DFS)です。 +グラフと木はいずれも、走査操作を実現するために探索アルゴリズムを用いる必要があります。グラフの走査方法も、幅優先走査深さ優先走査の 2 種類に分けられます。 -## 幅優先探索 +## 幅優先走査 -**幅優先探索は近くから遠くへの走査方法で、ある頂点から開始し、常に最も近い頂点を優先的に訪問し、層ごとに外側に展開していきます**。下図に示すように、左上の頂点から開始し、まずその頂点のすべての隣接頂点を走査し、次に次の頂点のすべての隣接頂点を走査し、以下同様に、すべての頂点が訪問されるまで続けます。 +**幅優先走査は、近いところから遠いところへ向かう走査方法であり、ある頂点から出発して、常に最も近い頂点を優先して訪問し、層ごとに外側へ広がっていきます**。以下の図に示すように、左上の頂点から出発し、まずその頂点のすべての隣接頂点を走査し、次に次の頂点のすべての隣接頂点を走査し、これを繰り返して、すべての頂点を訪問するまで続けます。 ![グラフの幅優先走査](graph_traversal.assets/graph_bfs.png) ### アルゴリズムの実装 -BFSは通常キューの助けを借りて実装されます(下記のコード参照)。キューは「先入先出」で、これは「近くから遠くへ」走査するBFSの考え方と一致します。 +BFS は通常キューを用いて実装され、コードは以下のとおりです。キューは「先入れ先出し」という性質を持ち、これは BFS の「近いところから遠いところへ」という考え方と本質的に一致しています。 -1. 開始頂点`startVet`をキューに追加し、ループを開始します。 -2. ループの各反復で、キューの先頭の頂点をポップし、それを訪問済みとして記録し、次にその頂点のすべての隣接頂点をキューの末尾に追加します。 -3. すべての頂点が訪問されるまで手順`2.`を繰り返します。 +1. 走査の開始頂点 `startVet` をキューに追加し、ループを開始します。 +2. ループの各反復で、キュー先頭の頂点を取り出して訪問を記録し、その後その頂点のすべての隣接頂点をキューの末尾に追加します。 +3. 手順 `2.` を繰り返し、すべての頂点が訪問されると終了します。 -頂点の再訪問を防ぐために、ハッシュセット`visited`を使用してどのノードが訪問されたかを記録します。 +頂点の重複走査を防ぐために、どの頂点が訪問済みかを記録するハッシュ集合 `visited` を用います。 + +!!! tip + + ハッシュ集合は、`value` を持たず `key` だけを格納するハッシュテーブルとみなせます。これは $O(1)$ の時間計算量で `key` の追加・削除・検索・更新を行えます。`key` の一意性にもとづき、ハッシュ集合は通常、データの重複排除などの場面で用いられます。 ```src [file]{graph_bfs}-[class]{}-[func]{graph_bfs} ``` -コードは比較的抽象的ですが、下図と比較することでより良く理解できます。 +コードはやや抽象的なので、以下の図と照らし合わせて理解を深めることを勧めます。 === "<1>" - ![グラフの幅優先探索の手順](graph_traversal.assets/graph_bfs_step1.png) + ![グラフの幅優先走査の手順](graph_traversal.assets/graph_bfs_step1.png) === "<2>" ![graph_bfs_step2](graph_traversal.assets/graph_bfs_step2.png) @@ -59,39 +63,39 @@ BFSは通常キューの助けを借りて実装されます(下記のコー === "<11>" ![graph_bfs_step11](graph_traversal.assets/graph_bfs_step11.png) -!!! question "幅優先走査のシーケンスは一意ですか?" +!!! question "幅優先走査の順序列は一意ですか?" - 一意ではありません。幅優先走査は「近くから遠く」の順序で走査することのみを要求し、**同じ距離の頂点の走査順序は任意にできます**。例えば、上図では、頂点$1$と$3$の訪問順序を交換できますし、頂点$2$、$4$、$6$の順序も同様です。 + 一意ではありません。幅優先走査は「近いところから遠いところへ」の順で走査することだけを要求し、**同じ距離にある複数の頂点の走査順は任意に入れ替えて構いません**。上図を例にすると、頂点 $1$ と $3$ の訪問順は交換でき、頂点 $2$、$4$、$6$ の訪問順も任意に入れ替えられます。 -### 計算量分析 +### 計算量の分析 -**時間計算量**:すべての頂点が一度ずつエンキューおよびデキューされ、$O(|V|)$時間を使用します。隣接頂点を走査する過程で、無向グラフであるため、すべての辺が$2$回訪問され、$O(2|E|)$時間を使用します。全体で$O(|V| + |E|)$時間を使用します。 +**時間計算量**:すべての頂点は 1 回ずつキューに入り、1 回ずつキューから出るため、$O(|V|)$ 時間です。隣接頂点を走査する過程では、無向グラフであるため、すべての辺が $2$ 回訪問され、$O(2|E|)$ 時間です。したがって全体では $O(|V| + |E|)$ 時間です。 -**空間計算量**:リスト`res`、ハッシュセット`visited`、キュー`que`の最大頂点数は$|V|$で、$O(|V|)$空間を使用します。 +**空間計算量**:リスト `res`、ハッシュ集合 `visited`、キュー `que` に含まれる頂点数は最大で $|V|$ であるため、$O(|V|)$ 空間です。 -## 深さ優先探索 +## 深さ優先走査 -**深さ優先探索は可能な限り遠くまで行き、それ以上のパスがない場合にバックトラックする走査方法です**。下図に示すように、左上の頂点から開始し、それ以上のパスがなくなるまで現在の頂点のいずれかの隣接頂点を訪問し、次に戻って続行し、すべての頂点が走査されるまで続けます。 +**深さ優先走査は、まず行けるところまで進み、進めなくなったら戻る走査方法です**。以下の図に示すように、左上の頂点から出発し、現在の頂点のある隣接頂点を訪問して、行き止まりに達するまで進んだら戻り、再び別の方向へ進んで行き止まりまで進んで戻る、ということを繰り返し、すべての頂点の走査が完了するまで続けます。 ![グラフの深さ優先走査](graph_traversal.assets/graph_dfs.png) ### アルゴリズムの実装 -この「可能な限り遠くまで行ってから戻る」アルゴリズムパラダイムは通常再帰に基づいて実装されます。幅優先探索と同様に、深さ優先探索でも、再訪問を避けるために訪問済み頂点を記録するハッシュセット`visited`の助けが必要です。 +この「行き止まりまで進んでから戻る」アルゴリズムのパターンは、通常再帰にもとづいて実装されます。幅優先走査と同様に、深さ優先走査でも、頂点の重複訪問を避けるために、訪問済みの頂点を記録するハッシュ集合 `visited` を用います。 ```src [file]{graph_dfs}-[class]{}-[func]{graph_dfs} ``` -深さ優先探索のアルゴリズムプロセスを下図に示します。 +深さ優先走査のアルゴリズムの流れは以下の図のとおりです。 -- **破線は下向きの再帰を表し**、新しい頂点を訪問するために新しい再帰メソッドが開始されたことを示します。 -- **曲線の破線は上向きのバックトラックを表し**、この再帰メソッドがこのメソッドが開始された位置に戻ったことを示します。 +- **直線の破線は下向きの再帰呼び出し**を表し、新しい頂点を訪問するために新たな再帰メソッドが開始されたことを意味します。 +- **曲線の破線は上向きのバックトラック**を表し、この再帰メソッドがすでに戻って、呼び出し元の位置までたどり着いたことを意味します。 -理解を深めるため、下図とコードを組み合わせて、DFSプロセス全体を頭の中でシミュレート(または描画)することをお勧めします。各再帰メソッドがいつ開始され、いつ戻るかを含めてです。 +理解を深めるために、以下の図とコードを結びつけて、DFS 全体の過程を頭の中でシミュレーションする(あるいは紙に書き出す)ことを勧めます。各再帰メソッドがいつ開始し、いつ戻るかも含めて追ってみてください。 === "<1>" - ![グラフの深さ優先探索の手順](graph_traversal.assets/graph_dfs_step1.png) + ![グラフの深さ優先走査の手順](graph_traversal.assets/graph_dfs_step1.png) === "<2>" ![graph_dfs_step2](graph_traversal.assets/graph_dfs_step2.png) @@ -123,14 +127,14 @@ BFSは通常キューの助けを借りて実装されます(下記のコー === "<11>" ![graph_dfs_step11](graph_traversal.assets/graph_dfs_step11.png) -!!! question "深さ優先走査のシーケンスは一意ですか?" +!!! question "深さ優先走査の順序列は一意ですか?" - 幅優先走査と同様に、深さ優先走査シーケンスの順序も一意ではありません。ある頂点が与えられた場合、どの方向を最初に探索することも可能です。つまり、隣接頂点の順序は任意にシャッフルできますが、すべて深さ優先走査の一部です。 + 幅優先走査と同様に、深さ優先走査の順序列も一意ではありません。ある頂点が与えられたとき、どの方向を先に探索してもよく、つまり隣接頂点の順序は任意に入れ替えられ、それでも深さ優先走査になります。 + + 木の走査を例にすると、「根 $\rightarrow$ 左 $\rightarrow$ 右」「左 $\rightarrow$ 根 $\rightarrow$ 右」「左 $\rightarrow$ 右 $\rightarrow$ 根」は、それぞれ先行順、中間順、後行順走査に対応します。これらは 3 種類の走査優先順位を示していますが、いずれも深さ優先走査に属します。 - 木の走査を例に取ると、「根 $\rightarrow$ 左 $\rightarrow$ 右」、「左 $\rightarrow$ 根 $\rightarrow$ 右」、「左 $\rightarrow$ 右 $\rightarrow$ 根」は、それぞれ前順、中順、後順走査に対応します。これらは3つの異なる走査優先度を示していますが、3つすべてが深さ優先走査と見なされます。 +### 計算量の分析 -### 計算量分析 +**時間計算量**:すべての頂点は $1$ 回ずつ訪問されるため、$O(|V|)$ 時間です。すべての辺は $2$ 回ずつ訪問されるため、$O(2|E|)$ 時間です。したがって全体では $O(|V| + |E|)$ 時間です。 -**時間計算量**:すべての頂点が一度訪問され、$O(|V|)$時間を使用します。すべての辺が2回訪問され、$O(2|E|)$時間を使用します。全体で$O(|V| + |E|)$時間を使用します。 - -**空間計算量**:リスト`res`、ハッシュセット`visited`の最大頂点数は$|V|$で、最大再帰深度は$|V|$です。したがって、$O(|V|)$空間を使用します。 +**空間計算量**:リスト `res` とハッシュ集合 `visited` に含まれる頂点数は最大で $|V|$ であり、再帰の深さも最大で $|V|$ であるため、$O(|V|)$ 空間です。 diff --git a/ja/docs/chapter_graph/index.md b/ja/docs/chapter_graph/index.md index 77e887fad..651aefb12 100644 --- a/ja/docs/chapter_graph/index.md +++ b/ja/docs/chapter_graph/index.md @@ -4,6 +4,6 @@ !!! abstract - 人生の旅路において、私たちの一人一人はノードであり、無数の見えない辺で結ばれています。 - - 一つ一つの出会いと別れが、この広大な人生のグラフに独特の印を残していきます。 + 人生の旅路において、私たちはそれぞれ一つひとつのノードのようなものであり、無数の見えない辺によって結ばれています。 + + 出会いと別れのたびに、この巨大なネットワークグラフの中に固有の足跡が刻まれます。 diff --git a/ja/docs/chapter_graph/summary.md b/ja/docs/chapter_graph/summary.md index 1fcb91235..10a02f30d 100644 --- a/ja/docs/chapter_graph/summary.md +++ b/ja/docs/chapter_graph/summary.md @@ -1,31 +1,31 @@ # まとめ -### 重要な復習 +### 重要なポイントの振り返り -- グラフは頂点と辺で構成されます。頂点の集合と辺の集合として記述できます。 -- 線形関係(連結リストなど)や階層関係(木など)と比較して、ネットワーク関係(グラフ)はより大きな柔軟性を提供し、より複雑になります。 -- 有向グラフでは、辺に方向があります。連結グラフでは、任意の頂点から他の任意の頂点に到達できます。重み付きグラフでは、各辺に関連する重み変数があります。 -- 隣接行列は、行列(2次元配列)を使用してグラフを表現する方法です。行と列は頂点を表します。行列要素の値は、2つの頂点間に辺があるかどうかを示し、辺がある場合は$1$、ない場合は$0$を使用します。隣接行列は辺の追加、削除、チェックなどの操作に非常に効率的ですが、より多くのスペースが必要です。 -- 隣接リストは、連結リストの集合を使用してグラフを表現するもう一つの一般的な方法です。グラフ内の各頂点には、その隣接するすべての頂点を含むリストがあります。$i$番目のリストは頂点$i$を表します。隣接リストは隣接行列と比較してより少ないスペースを使用します。ただし、辺を見つけるためにリストを走査する必要があるため、時間効率は低くなります。 -- 隣接リストの連結リストが十分に長くなったとき、ルックアップ効率を向上させるために赤黒木やハッシュテーブルに変換できます。 -- アルゴリズム設計の観点から、隣接行列は「空間と時間のトレードオフ」の概念を反映し、隣接リストは「時間と空間のトレードオフ」を反映します。 -- グラフは、ソーシャルネットワークや地下鉄路線など、さまざまな現実世界のシステムをモデル化するために使用できます。 -- 木はグラフの特別なケースであり、木の走査もグラフ走査の特別なケースです。 -- グラフの幅優先走査は、近くから遠くへと層ごとに拡張する探索方法で、通常キューを使用します。 -- グラフの深さ優先走査は、それ以上のパスがない場合にバックトラックする前に、まず終端に到達することを優先する探索方法です。しばしば再帰を使用して実装されます。 +- グラフは頂点と辺から構成され、一組の頂点と一組の辺からなる集合として表せます。 +- 線形関係(連結リスト)や分治関係(木)と比べて、ネットワーク関係(グラフ)は自由度が高く、そのぶん複雑です。 +- 有向グラフの辺は方向性を持ち、連結グラフでは任意の頂点に到達でき、重み付きグラフの各辺は重み変数を含みます。 +- 隣接行列は行列を用いてグラフを表し、各行(列)が 1 つの頂点を表し、行列要素が辺を表します。$1$ または $0$ を用いて、2 つの頂点の間に辺があるかないかを示します。隣接行列は追加・削除・検索・更新の操作効率が高い一方で、より多くの空間を消費します。 +- 隣接リストは複数の連結リストを使ってグラフを表し、第 $i$ 個の連結リストが頂点 $i$ に対応し、その頂点に隣接するすべての頂点を格納します。隣接リストは隣接行列よりも省スペースですが、辺を探すために連結リストを走査する必要があるため、時間効率は低くなります。 +- 隣接リスト内の連結リストが長くなりすぎた場合は、赤黒木やハッシュテーブルに変換することで、検索効率を高められます。 +- アルゴリズムの考え方という観点では、隣接行列は「空間を時間と引き換えにする」ことを体現し、隣接リストは「時間を空間と引き換えにする」ことを体現します。 +- グラフは、ソーシャルネットワークや地下鉄路線など、さまざまな現実のシステムをモデル化するために使えます。 +- 木はグラフの特殊な一例であり、木の走査もグラフ走査の特殊な一例です。 +- グラフの幅優先探索は、近いところから遠いところへ、層ごとに広がっていく探索方法であり、通常はキューを使って実装します。 +- グラフの深さ優先探索は、まず行けるところまで進み、進めなくなったらバックトラックする探索方法であり、通常は再帰に基づいて実装します。 ### Q & A -**Q**: パスは頂点のシーケンスとして定義されますか、それとも辺のシーケンスとして定義されますか? +**Q**:経路の定義は頂点列ですか、それとも辺列ですか? -グラフ理論では、グラフ内のパスは頂点のシーケンスを結ぶ有限または無限の辺のシーケンスです。 +Wikipedia では言語版ごとに定義が一致していません。英語版では「経路は辺の列」であり、中国語版では「経路は頂点の列」です。以下は英語版の原文です:In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices. -この文書では、パスは頂点のシーケンスではなく、辺のシーケンスと考えられます。これは、2つの頂点を結ぶ複数の辺がある可能性があり、その場合各辺がパスに対応するためです。 +本書では、経路を頂点列ではなく辺列とみなします。これは、2 つの頂点の間に複数の辺が存在する可能性があり、その場合は各辺がそれぞれ 1 本の経路に対応するためです。 -**Q**: 非連結グラフでは、走査できない点がありますか? +**Q**:非連結グラフには到達できない頂点がありますか? -非連結グラフでは、特定の点から到達できない頂点が少なくとも1つあります。非連結グラフを走査するには、グラフのすべての連結成分を走査するために複数の開始点を設定する必要があります。 +非連結グラフでは、ある頂点から出発すると、少なくとも 1 つの頂点には到達できません。非連結グラフ全体を走査するには、グラフ内のすべての連結成分をたどれるように複数の始点を設定する必要があります。 -**Q**: 隣接リストで、「その頂点に接続されたすべての頂点」の順序は重要ですか? +**Q**:隣接リストにおいて、「その頂点に接続されたすべての頂点」の順序に決まりはありますか? -任意の順序で構いません。ただし、実際のアプリケーションでは、頂点が追加された順序や頂点値の順序など、特定のルールに従ってそれらをソートする必要がある場合があります。これにより、特定の極値を持つ頂点を素早く見つけることができます。 +順序は任意でかまいません。ただし実際の応用では、頂点を追加した順序や頂点値の大小順など、特定の規則に従って並べ替える必要がある場合があります。そうすることで、「ある種の極値を持つ」頂点をすばやく見つけやすくなります。 diff --git a/ja/docs/chapter_greedy/fractional_knapsack_problem.md b/ja/docs/chapter_greedy/fractional_knapsack_problem.md index 38bad99f8..0c65ed88c 100644 --- a/ja/docs/chapter_greedy/fractional_knapsack_problem.md +++ b/ja/docs/chapter_greedy/fractional_knapsack_problem.md @@ -2,49 +2,51 @@ !!! question - $n$ 個のアイテムが与えられ、$i$ 番目のアイテムの重量は $wgt[i-1]$ で値は $val[i-1]$ です。容量が $cap$ のナップサックがあります。各アイテムは1回のみ選択できますが、**アイテムの一部を選択することができ、その値は選択された重量の割合に基づいて計算されます**。限られた容量の下でナップサック内のアイテムの最大値は何ですか?例を下の図に示します。 + $n$ 個の品物が与えられ、第 $i$ 個の品物の重さは $wgt[i-1]$、価値は $val[i-1]$ であり、容量が $cap$ のナップサックがある。各品物は 1 回だけ選択できるが、**品物の一部を選ぶこともでき、価値は選択した重量の割合に応じて計算される**。容量制限の下でナップサック内の品物の最大価値を求めよ。例を以下に示す。 ![分数ナップサック問題の例データ](fractional_knapsack_problem.assets/fractional_knapsack_example.png) -分数ナップサック問題は全体的に0-1ナップサック問題と非常に似ており、現在のアイテム $i$ と容量 $c$ を含み、ナップサックの限られた容量内で値を最大化することを目的としています。 +分数ナップサック問題は 0-1 ナップサック問題と全体として非常によく似ており、状態には現在の品物 $i$ と容量 $c$ が含まれ、目標は容量制限下での最大価値を求めることである。 -違いは、この問題ではアイテムの一部のみを選択できることです。下の図に示すように、**アイテムを任意に分割し、重量の割合に基づいて対応する値を計算できます**。 +異なる点は、本問では品物の一部だけを選べることである。以下に示すように、**品物は任意に分割でき、対応する価値は重量の割合に応じて計算される**。 -1. アイテム $i$ について、その単位重量あたりの値は $val[i-1] / wgt[i-1]$ で、単位値と呼ばれます。 -2. 重量 $w$ のアイテム $i$ の一部をナップサックに入れるとすると、ナップサックに追加される値は $w \times val[i-1] / wgt[i-1]$ です。 +1. 品物 $i$ について、単位重量あたりの価値は $val[i-1] / wgt[i-1]$ であり、これを単位価値と呼ぶ。 +2. 品物 $i$ の一部を重さ $w$ だけ入れると、ナップサックに増える価値は $w \times val[i-1] / wgt[i-1]$ となる。 -![アイテムの単位重量あたりの値](fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png) +![品物の単位重量あたりの価値](fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png) ### 貪欲戦略の決定 -ナップサック内のアイテムの総値を最大化することは、**本質的に単位重量あたりの値を最大化することを意味します**。これから、下の図に示す貪欲戦略を導出できます。 +ナップサック内の品物の総価値を最大化することは、**本質的には単位重量あたりの品物価値を最大化すること**である。そこから、以下に示す貪欲戦略を導ける。 -1. アイテムを単位値の高い順から低い順にソートします。 -2. すべてのアイテムを反復し、**各ラウンドで最も高い単位値を持つアイテムを貪欲に選択**します。 -3. ナップサックの残り容量が不十分な場合、現在のアイテムの一部を使用してナップサックを満たします。 +1. 品物を単位価値の高い順にソートする。 +2. すべての品物を走査し、**各回で単位価値が最も高い品物を貪欲に選択する**。 +3. 残りのナップサック容量が足りない場合は、現在の品物の一部を使ってナップサックを満たす。 ![分数ナップサック問題の貪欲戦略](fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png) ### コード実装 -アイテムを単位値でソートするために `Item` クラスを作成しました。ナップサックが満たされるまでループして貪欲な選択を行い、その後終了して解を返します: +品物を単位価値でソートできるように、`Item` クラスを定義する。貪欲選択を繰り返し、ナップサックが満杯になったら終了して解を返す。 ```src [file]{fractional_knapsack}-[class]{}-[func]{fractional_knapsack} ``` -ソート以外に、最悪の場合、アイテムのリスト全体を走査する必要があるため、**時間計算量は $O(n)$** です。ここで $n$ はアイテムの数です。 +組み込みのソートアルゴリズムの時間計算量は通常 $O(\log n)$、空間計算量は通常 $O(\log n)$ または $O(n)$ であり、具体的な値はプログラミング言語の実装に依存する。 -`Item` オブジェクトリストが初期化されるため、**空間計算量は $O(n)$** です。 +ソートを除けば、最悪の場合は品物リスト全体を走査する必要があるため、**時間計算量は $O(n)$** であり、ここで $n$ は品物数である。 + +`Item` オブジェクトのリストを初期化しているため、**空間計算量は $O(n)$** である。 ### 正しさの証明 -背理法を使用します。アイテム $x$ が最高の単位値を持ち、あるアルゴリズムが最大値 `res` を生成するが、解にアイテム $x$ が含まれていないと仮定します。 +背理法を用いる。品物 $x$ が単位価値最大の品物であり、あるアルゴリズムで得られた最大価値を `res` とするが、その解には品物 $x$ が含まれていないと仮定する。 -今、ナップサックから任意のアイテムの単位重量を取り除き、アイテム $x$ の単位重量で置き換えます。アイテム $x$ の単位値が最高であるため、置き換え後の総値は確実に `res` より大きくなります。**これは `res` が最適解であるという仮定と矛盾し、最適解には必ずアイテム $x$ が含まれることを証明します**。 +ここでナップサックから単位重量の任意の品物を取り出し、単位重量の品物 $x$ に置き換える。品物 $x$ の単位価値が最大であるため、置き換え後の総価値は必ず `res` より大きくなる。**これは `res` が最適解であることに矛盾し、最適解には必ず品物 $x$ が含まれなければならないことを示す**。 -この解の他のアイテムについても、上記の矛盾を構築できます。全体的に、**単位値がより大きいアイテムは常により良い選択**であり、貪欲戦略が効果的であることを証明します。 +この解に含まれる他の品物についても、同様の矛盾を構成できる。要するに、**単位価値がより大きい品物は常により良い選択である**。これは貪欲戦略が有効であることを示している。 -下の図に示すように、アイテムの重量と単位値をそれぞれ二次元チャートの横軸と縦軸と見なすと、分数ナップサック問題は「限られた横軸範囲内で囲まれる最大面積を求める」ことに変換できます。この類推は、幾何学的観点から貪欲戦略の効果を理解するのに役立ちます。 +以下に示すように、品物の重さと品物の単位価値をそれぞれ二次元グラフの横軸と縦軸とみなすと、分数ナップサック問題は「有限な横軸区間で囲まれる最大面積を求める問題」に変換できる。この類比は、幾何学的な観点から貪欲戦略の有効性を理解する助けになる。 ![分数ナップサック問題の幾何学的表現](fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png) diff --git a/ja/docs/chapter_greedy/greedy_algorithm.md b/ja/docs/chapter_greedy/greedy_algorithm.md index ac1aebf53..126778b9b 100644 --- a/ja/docs/chapter_greedy/greedy_algorithm.md +++ b/ja/docs/chapter_greedy/greedy_algorithm.md @@ -1,94 +1,94 @@ -# 貪欲アルゴリズム +# 貪欲法 -貪欲アルゴリズムは最適化問題を解決するための一般的なアルゴリズムで、基本的に問題の各意思決定段階で最も良い選択をすること、つまり局所的に最適な決定を貪欲に行い、グローバルに最適な解を見つけることを望みます。貪欲アルゴリズムは簡潔で効率的であり、多くの実用的な問題で広く使用されています。 +貪欲法(greedy algorithm)は、最適化問題を解くための一般的なアルゴリズムです。その基本的な考え方は、問題の各意思決定段階において、その時点で最善に見える選択を行い、すなわち貪欲に局所最適な決定を下すことで、大域最適解を得ようとするものです。貪欲法は簡潔で効率的であり、多くの実際の問題で広く用いられています。 -貪欲アルゴリズムと動的プログラミングは、どちらも最適化問題を解決するためによく使用されます。両者は最適部分構造の性質に依存するなど、いくつかの類似点を共有していますが、動作方法が異なります。 +貪欲法と動的計画法は、どちらも最適化問題を解く際によく用いられます。両者には、最適部分構造に依存するなどの共通点がありますが、その動作原理は異なります。 -- 動的プログラミングは現在の決定段階ですべての以前の決定を考慮し、過去の部分問題の解を使用して現在の部分問題の解を構築します。 -- 貪欲アルゴリズムは過去の決定を考慮せず、代わりに貪欲な選択を続け、問題が解決されるまで問題の範囲を継続的に狭めます。 +- 動的計画法は、前の段階までのすべての決定に基づいて現在の決定を考え、過去の部分問題の解を用いて現在の部分問題の解を構築します。 +- 貪欲法は過去の決定を考慮せず、ひたすら前に進みながら貪欲な選択を行い、問題の範囲を縮小し続けて、最終的に問題を解決します。 -まず、「完全ナップサック問題」の章で紹介された「コイン交換」の例を通じて貪欲アルゴリズムの動作原理を理解しましょう。すでによく知っていると思います。 +まずは例題「コイン両替」を通して、貪欲法の仕組みを理解しましょう。この問題はすでに「完全ナップサック問題」の節で紹介しているので、見覚えがあるはずです。 !!! question - $n$ 種類のコインが与えられ、$i$ 番目の種類のコインの額面は $coins[i - 1]$ で、目標金額は $amt$ です。各種類のコインは無制限に利用可能で、目標金額を構成するのに必要な最小コイン数は何ですか?目標金額を構成できない場合は $-1$ を返してください。 + $n$ 種類の硬貨が与えられ、$i$ 番目の硬貨の額面は $coins[i - 1]$ 、目標金額は $amt$ です。各硬貨は何度でも選べるとき、目標金額を作るために必要な最小の硬貨枚数を求めてください。目標金額を作れない場合は $-1$ を返します。 -この問題で採用される貪欲戦略を下の図に示します。目標金額が与えられたとき、**それに最も近く、それを超えないコインを貪欲に選択**し、目標金額が満たされるまでこのステップを繰り返します。 +この問題で採用する貪欲戦略は下図のとおりです。目標金額が与えられたら、**それを超えず、かつ最も近い硬貨を貪欲に選択し**、この手順を目標金額を作り切るまで繰り返します。 -![コイン交換の貪欲戦略](greedy_algorithm.assets/coin_change_greedy_strategy.png) +![コイン両替の貪欲戦略](greedy_algorithm.assets/coin_change_greedy_strategy.png) -実装コードは以下の通りです: +実装コードは次のとおりです。 ```src [file]{coin_change_greedy}-[class]{}-[func]{coin_change_greedy} ``` -感嘆するかもしれません:なんて簡潔なんだ!貪欲アルゴリズムは約10行のコードでコイン交換問題を解決します。 +思わずこう言いたくなるかもしれません。So clean!貪欲法はわずか十行ほどのコードでコイン両替問題を解いてしまいます。 -## 貪欲アルゴリズムの利点と制限 +## 貪欲法の利点と限界 -**貪欲アルゴリズムは直接的で実装が簡単であるだけでなく、通常非常に効率的でもあります**。上記のコードで、最小のコイン額面を $\min(coins)$ とすると、貪欲な選択ループは最大 $amt / \min(coins)$ 回実行され、時間計算量は $O(amt / \min(coins))$ になります。これは動的プログラミング解法の時間計算量 $O(n \times amt)$ よりも一桁小さいです。 +**貪欲法は操作が直接的で実装が簡単なだけでなく、通常は効率も高い**です。上のコードでは、硬貨の最小額面を $\min(coins)$ とすると、貪欲選択のループ回数は高々 $amt / \min(coins)$ 回であり、時間計算量は $O(amt / \min(coins))$ です。これは動的計画法による解法の時間計算量 $O(n \times amt)$ より 1 桁小さいオーダーです。 -しかし、**一部のコイン額面の組み合わせでは、貪欲アルゴリズムは最適解を見つけることができません**。下の図は2つの例を示しています。 +しかし、**硬貨の額面の組み合わせによっては、貪欲法では最適解を見つけられません**。下図に 2 つの例を示します。 -- **正の例 $coins = [1, 5, 10, 20, 50, 100]$**:このコインの組み合わせでは、任意の $amt$ に対して、貪欲アルゴリズムは最適解を見つけることができます。 -- **負の例 $coins = [1, 20, 50]$**:$amt = 60$ とすると、貪欲アルゴリズムは組み合わせ $50 + 1 \times 10$ しか見つけられず、合計11枚のコインですが、動的プログラミングは最適解 $20 + 20 + 20$ を見つけることができ、3枚のコインのみが必要です。 -- **負の例 $coins = [1, 49, 50]$**:$amt = 98$ とすると、貪欲アルゴリズムは組み合わせ $50 + 1 \times 48$ しか見つけられず、合計49枚のコインですが、動的プログラミングは最適解 $49 + 49$ を見つけることができ、2枚のコインのみが必要です。 +- **正例 $coins = [1, 5, 10, 20, 50, 100]$**:この硬貨の組み合わせでは、任意の $amt$ に対して貪欲法で最適解を見つけられます。 +- **反例 $coins = [1, 20, 50]$**:$amt = 60$ とすると、貪欲法では $50 + 1 \times 10$ という両替しか見つからず、硬貨は合計 $11$ 枚になります。しかし動的計画法なら最適解 $20 + 20 + 20$ を見つけられ、必要なのはわずか $3$ 枚です。 +- **反例 $coins = [1, 49, 50]$**:$amt = 98$ とすると、貪欲法では $50 + 1 \times 48$ という両替しか見つからず、硬貨は合計 $49$ 枚になります。しかし動的計画法なら最適解 $49 + 49$ を見つけられ、必要なのはわずか $2$ 枚です。 -![貪欲アルゴリズムが最適解を見つけられない例](greedy_algorithm.assets/coin_change_greedy_vs_dp.png) +![貪欲法では最適解を見つけられない例](greedy_algorithm.assets/coin_change_greedy_vs_dp.png) -これは、コイン交換問題において、貪欲アルゴリズムがグローバルに最適な解を見つけることを保証できず、非常に悪い解を見つける可能性があることを意味します。動的プログラミングの方が適しています。 +つまり、コイン両替問題に対して、貪欲法は大域最適解を保証できず、非常に悪い解を見つけてしまうこともあります。この問題は動的計画法で解くほうが適しています。 -一般的に、貪欲アルゴリズムの適用性は2つのカテゴリに分類されます。 +一般に、貪欲法が適用できる状況は次の 2 つに分けられます。 -1. **最適解を見つけることが保証される**:これらの場合、貪欲アルゴリズムはしばしば最良の選択で、バックトラッキングや動的プログラミングよりも効率的である傾向があります。 -2. **準最適解を見つけることができる**:貪欲アルゴリズムはここでも適用可能です。多くの複雑な問題では、グローバル最適解を見つけることは非常に困難であり、高効率の準最適解を見つけることも非常に価値があります。 +1. **最適解を保証できる場合**:この場合、貪欲法はしばしば最良の選択です。多くの場合、バックトラッキングや動的計画法より効率的だからです。 +2. **近似最適解を見つけられる場合**:この場合も貪欲法は有効です。多くの複雑な問題では、大域最適解を求めること自体が非常に難しく、より高い効率で準最適解を得られるだけでも十分価値があります。 -## 貪欲アルゴリズムの特徴 +## 貪欲法の特性 -それでは、どのような問題が貪欲アルゴリズムで解決するのに適しているのでしょうか?言い換えれば、どのような条件下で貪欲アルゴリズムは最適解を見つけることを保証できるのでしょうか? +では、どのような問題が貪欲法に適しているのでしょうか。言い換えると、貪欲法はどのような場合に最適解を保証できるのでしょうか。 -動的プログラミングと比較して、貪欲アルゴリズムはより厳しい使用条件を持ち、主に問題の2つの性質に焦点を当てています。 +動的計画法と比べると、貪欲法の適用条件はより厳しく、主に次の 2 つの性質に注目します。 -- **貪欲選択性**:局所的に最適な選択が常にグローバルに最適な解に導くことができる場合のみ、貪欲アルゴリズムは最適解を得ることを保証できます。 -- **最適部分構造**:元の問題の最適解はその部分問題の最適解を含みます。 +- **貪欲選択性**:局所最適な選択が常に大域最適解につながる場合にのみ、貪欲法は最適解を保証できます。 +- **最適部分構造**:元の問題の最適解が、部分問題の最適解を含むことです。 -最適部分構造は「動的プログラミング」の章ですでに紹介されているため、ここではこれ以上議論しません。一部の問題には明らかな最適部分構造がありませんが、それでも貪欲アルゴリズムを使用して解決できることに注意することが重要です。 +最適部分構造については「動的計画法」の節ですでに紹介したので、ここでは繰り返しません。なお、問題によっては最適部分構造が明確でなくても、貪欲法で解ける場合があります。 -主に貪欲選択性を決定する方法を探索します。その記述は単純に見えますが、**実際には、多くの問題の貪欲選択性を証明することは容易ではありません**。 +ここでは主に、貪欲選択性をどのように判定するかを考えます。説明だけを見ると単純そうですが、**実際には多くの問題で、貪欲選択性を証明するのは容易ではありません**。 -例えば、コイン交換問題では、貪欲選択性を反証するために反例を簡単に挙げることができますが、それを証明することははるかに困難です。**コインの組み合わせが貪欲アルゴリズムを使用して解決できるためには、どのような条件を満たす必要があるか**と尋ねられた場合、厳密な数学的証明を提供することが困難であるため、しばしば直感や例に頼って曖昧な答えを提供しなければなりません。 +たとえばコイン両替問題では、反例を挙げて貪欲選択性が成り立たないことを示すのは簡単ですが、成り立つことを証明するのは難しいです。もし、**どのような条件を満たす硬貨の組み合わせなら貪欲法で解けるのか**と問われると、直感や例示に頼った曖昧な答えしか出せず、厳密な数学的証明を与えるのは困難です。 !!! quote - ある論文では、コインの組み合わせが任意の金額に対して貪欲アルゴリズムを使用して最適解を見つけることができるかどうかを判定するための時間計算量 $O(n^3)$ のアルゴリズムが提示されています。 + ある論文では、ある硬貨の組み合わせについて、任意の金額に対する最適解を貪欲法で求められるかどうかを判定する、時間計算量 $O(n^3)$ のアルゴリズムが示されています。 Pearson, D. A polynomial-time algorithm for the change-making problem[J]. Operations Research Letters, 2005, 33(3): 231-234. -## 貪欲アルゴリズムによる問題解決のステップ +## 貪欲法の問題解決手順 -貪欲問題の問題解決プロセスは、一般的に以下の3つのステップに分けることができます。 +貪欲法による問題解決の流れは、おおむね次の 3 段階に分けられます。 -1. **問題分析**:問題の特徴を整理し理解する。状態定義、最適化目標、制約などを含みます。このステップはバックトラッキングや動的プログラミングでも関与します。 -2. **貪欲戦略の決定**:各ステップで貪欲な選択をする方法を決定する。この戦略は各ステップで問題の規模を縮小し、最終的に問題全体を解決できます。 -3. **正確性の証明**:通常、問題が貪欲選択性と最適部分構造の両方を持つことを証明する必要があります。このステップには、帰納法や背理法などの数学的証明が必要な場合があります。 +1. **問題分析**:状態の定義、最適化目標、制約条件などを整理し、問題の性質を理解します。この段階はバックトラッキングや動的計画法でも共通して現れます。 +2. **貪欲戦略の決定**:各ステップでどのように貪欲選択を行うかを定めます。この戦略により各ステップで問題規模を縮小し、最終的に問題全体を解決します。 +3. **正しさの証明**:通常は、その問題が貪欲選択性と最適部分構造を持つことを示す必要があります。この段階では、帰納法や背理法などの数学的証明が必要になることがあります。 -貪欲戦略の決定は問題解決の核心ステップですが、実装は容易ではない場合があります。主な理由は以下の通りです。 +貪欲戦略を定めることは問題解決の核心ですが、実際には簡単ではないことも多く、主な理由は次のとおりです。 -- **異なる問題間で貪欲戦略は大きく異なる**。多くの問題では、貪欲戦略はかなり直接的で、一般的な思考と試行を通じて思いつくことができます。しかし、一部の複雑な問題では、貪欲戦略は非常に見つけにくく、これは個人の問題解決経験とアルゴリズム能力の真のテストです。 -- **一部の貪欲戦略は非常に誤解を招く**。自信を持って貪欲戦略を設計し、コードを書いてテストに提出したとき、一部のテストケースが通らない可能性が高いです。これは設計された貪欲戦略が「部分的に正しい」だけであるためで、上記のコイン交換の例で説明した通りです。 +- **問題ごとに貪欲戦略の差が大きい**。多くの問題では貪欲戦略は比較的わかりやすく、おおまかな考察や試行だけで見つけられます。しかし複雑な問題では、貪欲戦略が非常に見えにくいことがあり、その場合は解法経験やアルゴリズム力が大きく問われます。 +- **一見もっともらしい貪欲戦略もある**。自信を持って貪欲戦略を設計し、コードを書いて提出しても、一部のテストケースを通過できないことがあります。これは、その貪欲戦略が「部分的にしか正しくない」ためであり、先ほどのコイン両替は典型例です。 -正確性を確保するために、貪欲戦略に対して厳密な数学的証明を提供すべきで、**通常は背理法や数学的帰納法を含みます**。 +正しさを保証するためには、貪欲戦略に対して厳密な数学的証明を行うべきであり、**通常は背理法や数学的帰納法が必要になります**。 -しかし、正確性を証明することは容易な作業ではない場合があります。途方に暮れた場合、通常はテストケースに基づいてコードをデバッグし、貪欲戦略を段階的に修正し検証することを選択します。 +しかし、正しさの証明もまた簡単とは限りません。手がかりがない場合には、テストケースを使ってコードをデバッグしながら、貪欲戦略を少しずつ修正して検証していくことがよくあります。 -## 貪欲アルゴリズムで解決される典型的な問題 +## 貪欲法の典型問題 -貪欲アルゴリズムは、貪欲選択と最適部分構造の性質を満たす最適化問題によく適用されます。以下は典型的な貪欲アルゴリズム問題のいくつかです。 +貪欲法は、貪欲選択性と最適部分構造を満たす最適化問題によく用いられます。以下に典型的な貪欲法の問題をいくつか挙げます。 -- **コイン交換問題**:一部のコインの組み合わせでは、貪欲アルゴリズムは常に最適解を提供します。 -- **区間スケジューリング問題**:いくつかのタスクがあり、それぞれが一定期間にわたって行われるとします。目標はできるだけ多くのタスクを完了することです。常に最も早く終了するタスクを選択すると、貪欲アルゴリズムは最適解を達成できます。 -- **分数ナップサック問題**:アイテムのセットと運搬容量が与えられ、目標は総重量が運搬容量を超えず、総価値が最大化されるようなアイテムのセットを選択することです。常に最高の価値対重量比(価値/重量)のアイテムを選択すると、貪欲アルゴリズムは一部のケースで最適解を達成できます。 -- **株式取引問題**:株価の履歴のセットが与えられ、複数回の取引を行うことができますが、すでに株式を所有している場合は売却後でないと再度購入できません。目標は最大利益を達成することです。 -- **ハフマン符号化**:ハフマン符号化は無損失データ圧縮に使用される貪欲アルゴリズムです。ハフマン木を構築することにより、常に最低頻度の2つのノードを統合し、最小重み付きパス長(符号化長)のハフマン木を生成します。 -- **ダイクストラのアルゴリズム**:これは与えられたソース頂点から他のすべての頂点への最短経路問題を解決するための貪欲アルゴリズムです。 +- **硬貨のお釣り問題**:ある種の硬貨の組み合わせでは、貪欲法で常に最適解が得られます。 +- **区間スケジューリング問題**:いくつかのタスクがあり、それぞれがある時間区間で実行されるとします。できるだけ多くのタスクを完了することが目標で、毎回終了時刻が最も早いタスクを選ぶなら、貪欲法で最適解を得られます。 +- **分数ナップサック問題**:一群の品物と積載容量が与えられたとき、総重量が容量を超えず、かつ総価値が最大になるように品物を選ぶ問題です。毎回、価値対重量比(価値 / 重量)が最も高い品物を選ぶなら、ある条件下で貪欲法は最適解を得られます。 +- **株式売買問題**:株価の履歴が与えられ、複数回の売買が可能ですが、すでに株を保有している場合は売却前に再度購入することはできません。目標は最大利益を得ることです。 +- **ハフマン符号化**:ハフマン符号化は、可逆データ圧縮に用いられる貪欲法です。ハフマン木を構築する際、毎回出現頻度が最も低い 2 つのノードを選んで併合すると、最終的に得られるハフマン木の重み付きパス長(符号長)は最小になります。 +- **Dijkstra アルゴリズム**:与えられた始点から他の各頂点への最短経路問題を解く貪欲法です。 diff --git a/ja/docs/chapter_greedy/index.md b/ja/docs/chapter_greedy/index.md index 7e3c599c3..33ca11c60 100644 --- a/ja/docs/chapter_greedy/index.md +++ b/ja/docs/chapter_greedy/index.md @@ -4,6 +4,6 @@ !!! abstract - ひまわりは太陽の方を向き、常に自分にとって最大の成長を求めます。 + ヒマワリは太陽に向かって回り、自らが最も大きく成長できる可能性を常に追い求める。 - 貪欲な戦略は、一連の単純な選択を通じて、段階的に最良の答えへと導きます。 + 貪欲戦略は、一回ごとの単純な選択を通じて、徐々に最適な答えへと導く。 diff --git a/ja/docs/chapter_greedy/max_capacity_problem.md b/ja/docs/chapter_greedy/max_capacity_problem.md index fe92678d5..6134f2bb9 100644 --- a/ja/docs/chapter_greedy/max_capacity_problem.md +++ b/ja/docs/chapter_greedy/max_capacity_problem.md @@ -2,51 +2,51 @@ !!! question - 配列 $ht$ を入力します。各要素は垂直仕切りの高さを表します。配列内の任意の2つの仕切りと、それらの間のスペースによってコンテナを形成できます。 + 配列 $ht$ が与えられ、各要素は垂直な仕切り板の高さを表します。配列内の任意の 2 枚の仕切り板と、その間の空間で容器を構成できます。 + + 容器の容量は高さと幅の積(面積)に等しく、高さは短い方の仕切り板で決まり、幅は 2 枚の仕切り板の配列インデックスの差です。 + + 配列から 2 枚の仕切り板を選び、構成される容器の容量が最大となるようにしてください。最大容量を返します。例を以下の図に示します。 - コンテナの容量は高さと幅の積(面積)で、高さは短い方の仕切りによって決定され、幅は2つの仕切りの配列インデックスの差です。 +![最大容量問題のサンプルデータ](max_capacity_problem.assets/max_capacity_example.png) - コンテナの容量を最大化する2つの仕切りを配列から選択し、この最大容量を返してください。例を下の図に示します。 +容器は任意の 2 枚の仕切り板で囲まれるため、**本問の状態は 2 枚の仕切り板のインデックスで表され、$[i, j]$ と記します**。 -![最大容量問題の例データ](max_capacity_problem.assets/max_capacity_example.png) - -コンテナは任意の2つの仕切りによって形成されるため、**この問題の状態は2つの仕切りのインデックスで表現され、$[i, j]$ と表記されます**。 - -問題の記述によれば、容量は高さと幅の積に等しく、高さは短い方の仕切りによって決定され、幅は2つの仕切りの配列インデックスの差です。容量 $cap[i, j]$ の式は: +問題の条件より、容量は高さと幅の積に等しく、高さは短い板で決まり、幅は 2 枚の仕切り板の配列インデックスの差です。容量を $cap[i, j]$ とすると、計算式は次のようになります。 $$ cap[i, j] = \min(ht[i], ht[j]) \times (j - i) $$ -配列の長さを $n$ と仮定すると、2つの仕切りの組み合わせ数(状態の総数)は $C_n^2 = \frac{n(n - 1)}{2}$ です。最も直接的なアプローチは**すべての可能な状態を列挙する**ことで、時間計算量は $O(n^2)$ になります。 +配列の長さを $n$ とすると、2 枚の仕切り板の組合せ数(状態総数)は $C_n^2 = \frac{n(n - 1)}{2}$ 個です。最も直接的には、**すべての状態を総当たりできます**。これにより最大容量を求められ、時間計算量は $O(n^2)$ です。 ### 貪欲戦略の決定 -この問題にはより効率的な解法があります。下の図に示すように、インデックス $i < j$ かつ高さ $ht[i] < ht[j]$ の状態 $[i, j]$ を選択します。つまり、$i$ は短い仕切り、$j$ は高い仕切りです。 +この問題にはさらに効率的な解法があります。以下の図のように、状態 $[i, j]$ を 1 つ選び、インデックスが $i < j$ かつ高さが $ht[i] < ht[j]$ を満たすとします。つまり、$i$ が短い板、$j$ が長い板です。 ![初期状態](max_capacity_problem.assets/max_capacity_initial_state.png) -下の図に示すように、**高い仕切り $j$ を短い仕切り $i$ に近づけて移動すると、容量は確実に減少します**。 +以下の図のように、**このとき長い板 $j$ を短い板 $i$ に近づけると、容量は必ず小さくなります**。 -これは、高い仕切り $j$ を移動すると、幅 $j-i$ が確実に減少するためです。高さは短い仕切りによって決定されるため、高さは同じまま($i$ が短い仕切りのまま)か減少(移動した $j$ が短い仕切りになる)しかありません。 +これは、長い板 $j$ を動かした後は幅 $j-i$ が必ず小さくなるためです。また、高さは短い板で決まるので、高さは変わらない( $i$ が依然として短い板)か、小さくなる(移動後の $j$ が短い板になる)ことしかありません。 -![高い仕切りを内側に移動した後の状態](max_capacity_problem.assets/max_capacity_moving_long_board.png) +![長い板を内側へ動かした後の状態](max_capacity_problem.assets/max_capacity_moving_long_board.png) -逆に、**短い仕切り $i$ を内側に移動することによってのみ容量を増加させることが可能です**。幅は確実に減少しますが、**高さが増加する可能性があります**(移動した短い仕切り $i$ が高くなる場合)。例えば、下の図では、短い仕切りを移動した後に面積が増加しています。 +逆に考えると、**短い板 $i$ を内側へ縮めた場合にのみ、容量が大きくなる可能性があります**。幅は必ず小さくなりますが、**高さは大きくなる可能性がある**からです(移動後の短い板 $i$ がより長くなる可能性があります)。たとえば次の図では、短い板を動かした後に面積が大きくなっています。 -![短い仕切りを内側に移動した後の状態](max_capacity_problem.assets/max_capacity_moving_short_board.png) +![短い板を内側へ動かした後の状態](max_capacity_problem.assets/max_capacity_moving_short_board.png) -これにより、この問題の貪欲戦略が導かれます:コンテナの両端に2つのポインタを初期化し、各ラウンドで短い仕切りに対応するポインタを内側に移動し、2つのポインタが出会うまで続けます。 +以上から、本問の貪欲戦略を導けます。2 本のポインタを初期化して容器の両端に置き、各ラウンドで短い板に対応するポインタを内側へ縮め、2 本のポインタが出会うまで続けます。 -下の図は貪欲戦略の実行を示しています。 +以下の図は、貪欲戦略の実行過程を示しています。 -1. 最初に、ポインタ $i$ と $j$ が配列の両端に配置されます。 +1. 初期状態では、ポインタ $i$ と $j$ は配列の両端にあります。 2. 現在の状態の容量 $cap[i, j]$ を計算し、最大容量を更新します。 -3. 仕切り $i$ と $j$ の高さを比較し、短い仕切りを1ステップ内側に移動します。 -4. $i$ と $j$ が出会うまでステップ `2.` と `3.` を繰り返します。 +3. 板 $i$ と板 $j$ の高さを比較し、短い板を内側へ 1 マス移動します。 +4. `2.` と `3.` を繰り返し実行し、$i$ と $j$ が出会ったら終了します。 === "<1>" - ![最大容量問題の貪欲プロセス](max_capacity_problem.assets/max_capacity_greedy_step1.png) + ![最大容量問題の貪欲な過程](max_capacity_problem.assets/max_capacity_greedy_step1.png) === "<2>" ![max_capacity_greedy_step2](max_capacity_problem.assets/max_capacity_greedy_step2.png) @@ -72,11 +72,11 @@ $$ === "<9>" ![max_capacity_greedy_step9](max_capacity_problem.assets/max_capacity_greedy_step9.png) -### 実装 +### コード実装 -コードは最大 $n$ 回ループするため、**時間計算量は $O(n)$** です。 +コードのループ回数は最大でも $n$ 回であるため、**時間計算量は $O(n)$** です。 -変数 $i$、$j$、$res$ は一定量の追加スペースを使用するため、**空間計算量は $O(1)$** です。 +変数 $i$、$j$、$res$ が使う追加領域は定数サイズなので、**空間計算量は $O(1)$** です。 ```src [file]{max_capacity}-[class]{}-[func]{max_capacity} @@ -84,16 +84,16 @@ $$ ### 正しさの証明 -貪欲法が列挙よりも高速である理由は、各ラウンドの貪欲選択が一部の状態を「スキップ」するからです。 +貪欲法が総当たりより速いのは、各ラウンドの貪欲な選択がいくつかの状態を「スキップ」するためです。 -例えば、$i$ が短い仕切りで $j$ が高い仕切りである状態 $cap[i, j]$ の下で、短い仕切り $i$ を貪欲に1ステップ内側に移動すると、下の図に示す「スキップされた」状態につながります。**これは、これらの状態の容量を後で検証できないことを意味します**。 +たとえば状態 $cap[i, j]$ において、$i$ が短い板、$j$ が長い板だとします。貪欲に短い板 $i$ を内側へ 1 マス動かすと、次の図に示す状態が「スキップ」されます。**これは、その後それらの状態の容量を検証できないことを意味します**。 $$ cap[i, i+1], cap[i, i+2], \dots, cap[i, j-2], cap[i, j-1] $$ -![短い仕切りの移動によってスキップされる状態](max_capacity_problem.assets/max_capacity_skipped_states.png) +![短い板の移動によってスキップされる状態](max_capacity_problem.assets/max_capacity_skipped_states.png) -観察すると、**これらのスキップされた状態は実際には高い仕切り $j$ が内側に移動したすべての状態**です。高い仕切りを内側に移動すると容量が確実に減少することをすでに証明しました。したがって、スキップされた状態は最適解である可能性がなく、**それらをスキップしても最適解を見逃すことはありません**。 +観察すると、**これらのスキップされた状態は、実際には長い板 $j$ を内側へ動かしたすべての状態そのものです**。前述のとおり、長い板を内側へ動かすと容量は必ず小さくなります。つまり、スキップされた状態はいずれも最適解にはなりえず、**それらを飛ばしても最適解を逃すことはありません**。 -分析により、短い仕切りを移動する操作は「安全」であり、貪欲戦略が効果的であることが示されます。 +以上の分析から、短い板を動かす操作は「安全」であり、貪欲戦略は有効であると分かります。 diff --git a/ja/docs/chapter_greedy/max_product_cutting_problem.md b/ja/docs/chapter_greedy/max_product_cutting_problem.md index 6dc436e4a..6c88c3b69 100644 --- a/ja/docs/chapter_greedy/max_product_cutting_problem.md +++ b/ja/docs/chapter_greedy/max_product_cutting_problem.md @@ -1,28 +1,28 @@ -# 最大積切断問題 +# 最大積分割問題 !!! question - 正の整数 $n$ が与えられたとき、それを合計が $n$ になる少なくとも2つの正の整数に分割し、これらの整数の最大積を求めてください。下の図に示すとおりです。 + 正整数 $n$ が与えられたとき、それを少なくとも 2 つの正整数の和に分割し、分割後のすべての整数の積の最大値を求めよ。下図に示す。 -![最大積切断問題の定義](max_product_cutting_problem.assets/max_product_cutting_definition.png) +![最大積分割問題の定義](max_product_cutting_problem.assets/max_product_cutting_definition.png) -$n$ を $m$ 個の整数因子に分割すると仮定し、$i$ 番目の因子を $n_i$ と表記すると、 +仮に $n$ を $m$ 個の整数因子に分割し、そのうち第 $i$ 個の因子を $n_i$ と記すと、 $$ n = \sum_{i=1}^{m}n_i $$ -この問題の目標は、すべての整数因子の最大積を見つけることです。すなわち、 +本問題の目的は、すべての整数因子の積の最大値を求めることであり、すなわち $$ \max(\prod_{i=1}^{m}n_i) $$ -考慮すべき点:分割数 $m$ はどの程度大きくすべきか、各 $n_i$ は何であるべきか? +考えるべきことは、分割数 $m$ をいくつにすべきか、各 $n_i$ をいくつにすべきかである。 ### 貪欲戦略の決定 -経験的に、2つの整数の積は多くの場合その和よりも大きくなります。$n$ から因子 $2$ を分割すると仮定すると、その積は $2(n-2)$ です。この積を $n$ と比較します: +経験的に、2 つの整数の積はその和より大きくなることが多い。$n$ から因子 $2$ を 1 つ切り出すと、それらの積は $2(n-2)$ となる。この積を $n$ と比較すると、 $$ \begin{aligned} @@ -32,54 +32,54 @@ n & \geq 4 \end{aligned} $$ -下の図に示すように、$n \geq 4$ のとき、$2$ を分割すると積が増加します。**これは4以上の整数を分割すべきであることを示しています**。 +下図のように、$n \geq 4$ のとき、$2$ を 1 つ切り出すと積は大きくなる。**これは、$4$ 以上の整数はすべて分割すべきことを意味する**。 -**貪欲戦略1**:分割スキームが $\geq 4$ の因子を含む場合、それらはさらに分割されるべきです。最終的な分割は因子 $1$、$2$、$3$ のみを含むべきです。 +**貪欲戦略一**:分割方法に $\geq 4$ の因子が含まれるなら、それはさらに分割すべきである。最終的な分割方法に現れる因子は $1$、$2$、$3$ の 3 種類だけである。 -![分割による積の増加](max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png) +![分割により積が大きくなる](max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png) -次に、どの因子が最適かを考慮します。因子 $1$、$2$、$3$ の中で、明らかに $1$ が最悪です。$1 \times (n-1) < n$ が常に成り立つため、$1$ を分割すると実際に積が減少します。 +次に、どの因子が最適かを考える。$1$、$2$、$3$ の 3 つの因子のうち、明らかに $1$ が最も悪い。なぜなら $1 \times (n-1) < n$ は常に成り立ち、$1$ を切り出すとかえって積が小さくなるからである。 -下の図に示すように、$n = 6$ のとき、$3 \times 3 > 2 \times 2 \times 2$ です。**これは $3$ を分割する方が $2$ を分割するよりも良いことを意味します**。 +下図のように、$n = 6$ のとき、$3 \times 3 > 2 \times 2 \times 2$ が成り立つ。**これは、$2$ を切り出すより $3$ を切り出すほうが有利であることを意味する**。 -**貪欲戦略2**:分割スキームには最大で2つの $2$ があるべきです。3つの $2$ は常に2つの $3$ に置き換えてより高い積を得ることができるからです。 +**貪欲戦略二**:分割方法の中に存在してよい $2$ は高々 2 つである。なぜなら、3 つの $2$ は常に 2 つの $3$ に置き換えられ、より大きな積を得られるからである。 ![最適な分割因子](max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png) -上記から、以下の貪欲戦略を導出できます。 +以上より、次の貪欲戦略が導かれる。 -1. 入力整数 $n$ について、余りが $0$、$1$、または $2$ になるまで因子 $3$ を継続的に分割します。 -2. 余りが $0$ の場合、$n$ が $3$ の倍数であることを意味するため、それ以上の行動は取りません。 -3. 余りが $2$ の場合、さらに分割を続けず、そのまま保持します。 -4. 余りが $1$ の場合、$2 \times 2 > 1 \times 3$ であるため、最後の $3$ を $2$ に置き換えるべきです。 +1. 整数 $n$ を入力し、余りが $0$、$1$、$2$ になるまで、そこから因子 $3$ を繰り返し切り出す。 +2. 余りが $0$ のとき、$n$ は $3$ の倍数であることを表すため、何も処理しない。 +3. 余りが $2$ のときは、それ以上分割せず、そのまま残す。 +4. 余りが $1$ のとき、$2 \times 2 > 1 \times 3$ であるため、最後の $3$ を $2$ に置き換えるべきである。 ### コード実装 -下の図に示すように、整数を分割するためにループを使用する必要はなく、床除算演算を使用して $3$ の数 $a$ を取得し、剰余演算を使用して余り $b$ を取得できます。したがって: +下図のように、ループで整数を分割する必要はなく、切り捨て除算によって $3$ の個数 $a$ を、剰余演算によって余り $b$ を得られる。このとき、 $$ -n = 3a + b +n = 3 a + b $$ -$n \leq 3$ の境界ケースでは、$1$ を分割する必要があり、積は $1 \times (n - 1)$ であることに注意してください。 +なお、$n \leq 3$ の境界ケースでは、必ず $1$ を 1 つ分割する必要があり、積は $1 \times (n - 1)$ となる。 ```src [file]{max_product_cutting}-[class]{}-[func]{max_product_cutting} ``` -![切断後の最大積の計算方法](max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png) +![最大積分割の計算方法](max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png) -**時間計算量はプログラミング言語のべき乗演算の実装に依存します**。Pythonでは、よく使用されるべき乗計算関数は3種類あります: +**時間計算量は、プログラミング言語におけるべき乗演算の実装方法に依存する**。Python を例に取ると、よく使われるべき乗計算関数は 3 種類ある。 -- 演算子 `**` と関数 `pow()` の両方の時間計算量は $O(\log⁡ a)$ です。 -- `math.pow()` 関数は内部でC言語ライブラリの `pow()` 関数を呼び出し、浮動小数点べき乗を実行し、時間計算量は $O(1)$ です。 +- 演算子 `**` と関数 `pow()` の時間計算量はいずれも $O(\log⁡ a)$ である。 +- 関数 `math.pow()` は内部で C 言語ライブラリの `pow()` 関数を呼び出し、浮動小数点のべき乗を実行するため、時間計算量は $O(1)$ である。 -変数 $a$ と $b$ は一定サイズの追加スペースを使用するため、**空間計算量は $O(1)$** です。 +変数 $a$ と $b$ が使う追加領域は定数サイズであり、**したがって空間計算量は $O(1)$ である**。 ### 正しさの証明 -背理法を使用し、$n \geq 3$ のケースのみを分析します。 +背理法を用い、$n \geq 4$ の場合のみを考える。 -1. **すべての因子 $\leq 3$**:最適分割スキームが因子 $x \geq 4$ を含むと仮定すると、それを確実に $2(x-2)$ にさらに分割でき、より大きな積を得られます。これは仮定と矛盾します。 -2. **分割スキームに $1$ が含まれない**:最適分割スキームが因子 $1$ を含むと仮定すると、それを確実に別の因子と結合してより大きな積を得られます。これは仮定と矛盾します。 -3. **分割スキームには最大で2つの $2$ が含まれる**:最適分割スキームが3つの $2$ を含むと仮定すると、それらを確実に2つの $3$ に置き換えて、より高い積を達成できます。これは仮定と矛盾します。 +1. **すべての因子は $\leq 3$** :最適な分割方法に $\geq 4$ の因子 $x$ が存在すると仮定すると、それは必ずさらに $2(x-2)$ に分割でき、より大きい(または等しい)積が得られる。これは仮定に矛盾する。 +2. **分割方法に $1$ は含まれない** :最適な分割方法に因子 $1$ が 1 つ存在すると仮定すると、それは必ず別の因子に併合でき、より大きい積を得られる。これは仮定に矛盾する。 +3. **分割方法に含まれる $2$ は高々 2 つ** :最適な分割方法に 3 つの $2$ が含まれると仮定すると、それは必ず 2 つの $3$ に置き換えられ、積はより大きくなる。これは仮定に矛盾する。 diff --git a/ja/docs/chapter_greedy/summary.md b/ja/docs/chapter_greedy/summary.md index bcae5fd0a..f2d373a2c 100644 --- a/ja/docs/chapter_greedy/summary.md +++ b/ja/docs/chapter_greedy/summary.md @@ -1,12 +1,14 @@ # まとめ -- 貪欲アルゴリズムは最適化問題を解決するためによく使用され、原理は各決定段階で局所的に最適な決定を行い、グローバルに最適な解を達成することです。 -- 貪欲アルゴリズムは貪欲な選択を次々と反復的に行い、各ラウンドで問題をより小さな部分問題に変換し、問題が解決されるまで続けます。 -- 貪欲アルゴリズムは実装が簡単なだけでなく、問題解決効率も高いです。動的プログラミングと比較して、貪欲アルゴリズムは一般的により低い時間計算量を持ちます。 -- コイン交換問題において、貪欲アルゴリズムは特定のコインの組み合わせに対して最適解を保証できますが、他の組み合わせでは貪欲アルゴリズムが非常に悪い解を見つける可能性があります。 -- 貪欲アルゴリズム解法に適した問題は2つの主要な性質を持ちます:貪欲選択性と最適部分構造。貪欲選択性は貪欲戦略の効果を表します。 -- 一部の複雑な問題では、貪欲選択性を証明することは簡単ではありません。逆に、無効性を証明することはしばしばより容易で、コイン交換問題などがその例です。 -- 貪欲問題の解決は主に3つのステップから構成されます:問題分析、貪欲戦略の決定、正しさの証明。このうち、貪欲戦略の決定が重要なステップであり、正しさの証明がしばしば挑戦となります。 -- 分数ナップサック問題は0-1ナップサック問題に基づいてアイテムの一部の選択を可能にし、したがって貪欲アルゴリズムを使用して解決できます。貪欲戦略の正しさは背理法によって証明できます。 -- 最大容量問題は全探索法で解決でき、時間計算量は $O(n^2)$ です。貪欲戦略を設計することで、各ラウンドで短い板を内側に移動し、時間計算量を $O(n)$ に最適化します。 -- 切断後の最大積問題において、2つの貪欲戦略を導出します:$\geq 4$ の整数は継続的に切断されるべきで、最適な切断因子は $3$ です。コードにはべき乗演算が含まれ、時間計算量はべき乗演算の実装方法に依存し、一般的に $O(1)$ または $O(\log n)$ です。 +### 重要な振り返り + +- 貪欲法は通常、最適化問題を解くために用いられ、その原理は各意思決定段階で局所最適な決定を行い、全体最適解を得ることを目指すというものである。 +- 貪欲法は反復的に次々と貪欲な選択を行い、各ラウンドで問題をより小さな部分問題へと変換し、最終的に問題を解決する。 +- 貪欲法は実装が簡単であるだけでなく、問題を解く効率も高い。動的計画法と比べると、貪欲法の時間計算量は通常より低い。 +- 硬貨両替問題では、ある種の硬貨の組み合わせに対しては貪欲法で最適解を保証できるが、別の組み合わせではそうではなく、非常に悪い解を見つけてしまう可能性がある。 +- 貪欲法による解法に適した問題は、貪欲選択性と最適部分構造という 2 つの性質を備えている。貪欲選択性は、貪欲戦略の有効性を表している。 +- 一部の複雑な問題では、貪欲選択性を証明するのは容易ではない。相対的には、反例による否定のほうが簡単であり、硬貨両替問題がその一例である。 +- 貪欲法の問題を解く流れは主に 3 段階に分かれる。すなわち、問題分析、貪欲戦略の決定、正しさの証明である。このうち、貪欲戦略の決定が中核であり、正しさの証明はしばしば難所となる。 +- 分数ナップサック問題は 0-1 ナップサックを基に、品物の一部を選ぶことを許しているため、貪欲法で解くことができる。貪欲戦略の正しさは背理法で証明できる。 +- 最大容量問題は全探索で解くことができ、時間計算量は $O(n^2)$ である。貪欲戦略を設計し、各ラウンドで短い板を内側へ動かすことで、時間計算量を $O(n)$ に最適化できる。 +- 最大分割積問題では、2 つの貪欲戦略を順に導いた。すなわち、$\geq 4$ の整数はすべてさらに分割すべきであり、最適な分割因子は $3$ である。コードにはべき乗演算が含まれており、時間計算量はその実装方法に依存し、通常は $O(1)$ または $O(\log n)$ である。 diff --git a/ja/docs/chapter_hashing/hash_algorithm.md b/ja/docs/chapter_hashing/hash_algorithm.md index 332dae4f0..90ef06095 100644 --- a/ja/docs/chapter_hashing/hash_algorithm.md +++ b/ja/docs/chapter_hashing/hash_algorithm.md @@ -1,60 +1,60 @@ # ハッシュアルゴリズム -前の2つの節では、ハッシュ表の動作原理とハッシュ衝突を処理する方法を紹介しました。しかし、オープンアドレス法と連鎖法はどちらも**衝突が発生した際にハッシュ表が正常に機能することのみを保証でき、ハッシュ衝突の発生頻度を減らすことはできません**。 +前の 2 節では、ハッシュテーブルの動作原理とハッシュ衝突の処理方法を紹介しました。しかし、オープンアドレス法であれ連鎖方式であれ、**それらが保証できるのは衝突発生時でもハッシュテーブルが正常に動作することだけであり、ハッシュ衝突そのものを減らすことはできません**。 -ハッシュ衝突があまりにも頻繁に発生すると、ハッシュ表の性能は劇的に悪化します。下図に示すように、連鎖法ハッシュ表では、理想的なケースではキー値ペアがバケット間に均等に分散され、最適なクエリ効率を実現します。最悪のケースでは、すべてのキー値ペアが同じバケットに格納され、時間計算量が$O(n)$に悪化します。 +ハッシュ衝突があまりにも頻繁に発生すると、ハッシュテーブルの性能は急激に劣化します。下図のように、連鎖方式のハッシュテーブルでは、理想的な場合にはキーと値のペアが各バケットに均等に分布し、最良の検索効率を達成します。最悪の場合には、すべてのキーと値のペアが同じバケットに格納され、時間計算量は $O(n)$ に劣化します。 -![ハッシュ衝突の理想的および最悪のケース](hash_algorithm.assets/hash_collision_best_worst_condition.png) +![ハッシュ衝突の最良ケースと最悪ケース](hash_algorithm.assets/hash_collision_best_worst_condition.png) -**キー値ペアの分布はハッシュ関数によって決定されます**。ハッシュ関数の計算ステップを思い出すと、まずハッシュ値を計算し、次に配列長で剰余を取ります: +**キーと値のペアの分布はハッシュ関数によって決まります**。ハッシュ関数の計算手順を思い出すと、まずハッシュ値を計算し、その後で配列長に対して剰余を取ります。 ```shell index = hash(key) % capacity ``` -上記の式を観察すると、ハッシュ表の容量`capacity`が固定されている場合、**ハッシュアルゴリズム`hash()`が出力値を決定し**、それによってハッシュ表におけるキー値ペアの分布を決定します。 +上の式から分かるように、ハッシュテーブルの容量 `capacity` が固定されているとき、**出力値を決めるのはハッシュアルゴリズム `hash()` です**。したがって、それがキーと値のペアのハッシュテーブル内での分布も決定します。 -これは、ハッシュ衝突の確率を減らすために、ハッシュアルゴリズム`hash()`の設計に焦点を当てるべきであることを意味します。 +これは、ハッシュ衝突の発生確率を下げるには、ハッシュアルゴリズム `hash()` の設計に注目すべきだということを意味します。 ## ハッシュアルゴリズムの目標 -「高速で安定した」ハッシュ表データ構造を実現するために、ハッシュアルゴリズムは以下の特性を持つべきです: +「高速かつ安定した」ハッシュテーブルというデータ構造を実現するために、ハッシュアルゴリズムは次の特徴を備える必要があります。 -- **決定性**: 同じ入力に対して、ハッシュアルゴリズムは常に同じ出力を生成するべきです。そうでなければハッシュ表は信頼できません。 -- **高効率**: ハッシュ値を計算するプロセスは十分に高速である必要があります。計算オーバーヘッドが小さいほど、ハッシュ表はより実用的になります。 -- **均等分散**: ハッシュアルゴリズムはキー値ペアがハッシュ表に均等に分散されることを保証するべきです。分散が均等であるほど、ハッシュ衝突の確率は低くなります。 +- **決定性**:同じ入力に対して、ハッシュアルゴリズムは常に同じ出力を生成しなければなりません。そうして初めて、ハッシュテーブルの信頼性が保たれます。 +- **高効率**:ハッシュ値の計算過程は十分に高速であるべきです。計算コストが小さいほど、ハッシュテーブルの実用性は高くなります。 +- **均一分布**:ハッシュアルゴリズムは、キーと値のペアがハッシュテーブル内に均等に分布するようにすべきです。分布が均一であるほど、ハッシュ衝突の確率は低くなります。 -実際、ハッシュアルゴリズムはハッシュ表の実装だけでなく、他の分野でも広く応用されています。 +実際には、ハッシュアルゴリズムはハッシュテーブルの実装だけでなく、ほかの多くの分野でも広く利用されています。 -- **パスワード保存**: ユーザーパスワードのセキュリティを保護するために、システムは通常平文パスワードを保存せず、パスワードのハッシュ値を保存します。ユーザーがパスワードを入力すると、システムは入力のハッシュ値を計算し、保存されているハッシュ値と比較します。一致すれば、パスワードは正しいと見なされます。 -- **データ整合性チェック**: データ送信者はデータのハッシュ値を計算して一緒に送信できます。受信者は受信したデータのハッシュ値を再計算し、受信したハッシュ値と比較できます。一致すれば、データは完全であると見なされます。 +- **パスワード保存**:ユーザーのパスワードを保護するために、システムは通常、平文パスワードを直接保存せず、そのハッシュ値を保存します。ユーザーがパスワードを入力すると、システムは入力内容のハッシュ値を計算し、保存済みのハッシュ値と比較します。一致すれば、そのパスワードは正しいと見なされます。 +- **データ完全性検査**:送信側はデータのハッシュ値を計算してデータと一緒に送信できます。受信側は受け取ったデータのハッシュ値を再計算し、受信したハッシュ値と比較できます。両者が一致すれば、そのデータは完全だと見なされます。 -暗号化アプリケーションでは、ハッシュ値から元のパスワードを推測するなどの逆行分析を防ぐために、ハッシュアルゴリズムはより高いレベルのセキュリティ機能が必要です。 +暗号分野の応用では、ハッシュ値から元のパスワードを推測するといった逆解析を防ぐために、ハッシュアルゴリズムにはさらに高いレベルの安全性が求められます。 -- **一方向性**: ハッシュ値から入力データに関する情報を推測することは不可能であるべきです。 -- **衝突耐性**: 同じハッシュ値を生成する2つの異なる入力を見つけることは極めて困難であるべきです。 -- **雪崩効果**: 入力の小さな変更は、出力に大きく予測不可能な変化をもたらすべきです。 +- **一方向性**:ハッシュ値から入力データに関するいかなる情報も逆算できないこと。 +- **耐衝突性**:異なる 2 つの入力で同じハッシュ値になるものを見つけることが、極めて困難であること。 +- **アバランシェ効果**:入力のわずかな変化が、出力の大きく予測不能な変化を引き起こすこと。 -**「均等分散」と「衝突耐性」は2つの別々の概念**であることに注意してください。均等分散を満たしても、必ずしも衝突耐性があるとは限りません。例えば、ランダムな入力`key`の下で、ハッシュ関数`key % 100`は均等に分散された出力を生成できます。しかし、このハッシュアルゴリズムは過度にシンプルで、下二桁が同じすべての`key`は同じ出力を持つため、ハッシュ値から使用可能な`key`を簡単に推測でき、パスワードを破ることができます。 +注意してほしいのは、**「均一分布」と「耐衝突性」は独立した 2 つの概念である**という点です。均一分布を満たしていても、耐衝突性を満たすとは限りません。たとえば、入力 `key` がランダムである場合、ハッシュ関数 `key % 100` は均一に分布した出力を生成できます。しかし、このハッシュアルゴリズムはあまりにも単純で、下 2 桁が同じ `key` はすべて同じ出力になります。そのため、ハッシュ値から利用可能な `key` を容易に逆算でき、結果としてパスワードが破られてしまいます。 ## ハッシュアルゴリズムの設計 -ハッシュアルゴリズムの設計は多くの要因を考慮する必要がある複雑な問題です。しかし、要求が少ない一部のシナリオでは、いくつかの簡単なハッシュアルゴリズムを設計することもできます。 +ハッシュアルゴリズムの設計は、多くの要素を考慮しなければならない複雑な問題です。しかし、要求の高くない場面であれば、いくつかの単純なハッシュアルゴリズムを設計することもできます。 -- **加算ハッシュ**: 入力の各文字のASCIIコードを合計し、合計をハッシュ値として使用します。 -- **乗算ハッシュ**: 乗算の非相関性を利用し、各ラウンドで定数を乗算し、各文字のASCIIコードをハッシュ値に累積します。 -- **XORハッシュ**: 入力データの各要素をXORすることでハッシュ値を累積します。 -- **回転ハッシュ**: 各文字のASCIIコードをハッシュ値に累積し、各累積前にハッシュ値に回転操作を実行します。 +- **加算ハッシュ**:入力の各文字の ASCII コードを足し合わせ、その合計をハッシュ値とします。 +- **乗算ハッシュ**:乗算の非相関性を利用し、各ラウンドで定数を掛けながら、各文字の ASCII コードをハッシュ値に累積します。 +- **XOR ハッシュ**:入力データの各要素を XOR 演算で 1 つのハッシュ値に累積します。 +- **回転ハッシュ**:各文字の ASCII コードを 1 つのハッシュ値に累積し、各回の累積前にハッシュ値を回転させます。 ```src [file]{simple_hash}-[class]{}-[func]{rot_hash} ``` -各ハッシュアルゴリズムの最後のステップが大きな素数$1000000007$の剰余を取ることで、ハッシュ値が適切な範囲内にあることを保証していることが観察されます。なぜ素数の剰余を取ることが強調されるのか、または合成数の剰余を取ることの欠点は何かを考える価値があります。これは興味深い質問です。 +見て分かるように、各ハッシュアルゴリズムの最後のステップでは、大きな素数 $1000000007$ で剰余を取り、ハッシュ値が適切な範囲に収まるようにしています。ここで考えてみる価値があるのは、なぜ素数での剰余を強調するのか、あるいは合成数で剰余を取ることにどんな欠点があるのか、という点です。これは興味深い問題です。 -結論として:**大きな素数を剰余として使用することで、ハッシュ値の均等分散を最大化できます**。素数は他の数と共通因子を持たないため、剰余演算によって引き起こされる周期的パターンを減らし、ハッシュ衝突を回避できます。 +先に結論を述べると、**法として大きな素数を使うと、ハッシュ値が均一に分布することを最大限に保証できます**。素数はほかの数と公約数を持たないため、剰余演算によって生じる周期的なパターンを減らし、ハッシュ衝突を避けやすくなります。 -例えば、合成数$9$を剰余として選択するとします。これは$3$で割り切れるため、$3$で割り切れるすべての`key`はハッシュ値$0$、$3$、$6$にマッピングされます。 +たとえば、法として合成数 $9$ を選ぶとします。これは $3$ で割り切れるため、$3$ で割り切れるすべての `key` は、$0$、$3$、$6$ の 3 つのハッシュ値に写像されます。 $$ \begin{aligned} @@ -64,7 +64,7 @@ $$ \end{aligned} $$ -入力`key`がたまたまこの種の等差数列分布を持つ場合、ハッシュ値がクラスターし、ハッシュ衝突を悪化させます。今度は`modulus`を素数$13$に置き換えるとします。`key`と`modulus`の間に共通因子がないため、出力ハッシュ値の均等性が大幅に改善されます。 +入力 `key` がたまたまこのような等差数列の分布をしていると、ハッシュ値に偏りが生じ、ハッシュ衝突がさらに深刻になります。そこで `modulus` を素数 $13$ に置き換えると仮定すると、`key` と `modulus` の間に公約数が存在しないため、出力されるハッシュ値の均一性は明らかに向上します。 $$ \begin{aligned} @@ -74,71 +74,71 @@ $$ \end{aligned} $$ -`key`がランダムで均等に分散されることが保証されている場合、剰余として素数または合成数を選択しても、両方とも均等に分散されたハッシュ値を生成できることは注目に値します。しかし、`key`の分布にある種の周期性がある場合、合成数の剰余はクラスタリングを引き起こしやすくなります。 +補足すると、`key` がランダムかつ均一に分布していると保証できるなら、法に素数を選んでも合成数を選んでも構いません。どちらでも均一に分布したハッシュ値を出力できます。しかし、`key` の分布に何らかの周期性がある場合、合成数で剰余を取るほうが偏りが生じやすくなります。 -要約すると、通常は素数を剰余として選択し、この素数は周期的パターンを可能な限り排除し、ハッシュアルゴリズムの堅牢性を向上させるために十分大きくある必要があります。 +要するに、通常は法として素数を選び、その素数はできるだけ大きいほうが望ましいです。そうすることで周期的なパターンをできる限り取り除き、ハッシュアルゴリズムの堅牢性を高められます。 ## 一般的なハッシュアルゴリズム -上記で言及した簡単なハッシュアルゴリズムはかなり「脆弱」で、ハッシュアルゴリズムの設計目標から程遠いことは難しくありません。例えば、加算とXORは交換法則に従うため、加算ハッシュとXORハッシュは同じ内容だが順序が異なる文字列を区別できず、ハッシュ衝突を悪化させ、セキュリティ問題を引き起こす可能性があります。 +上で紹介した単純なハッシュアルゴリズムは、どれも比較的「脆弱」であり、ハッシュアルゴリズムの設計目標にはほど遠いことが分かります。たとえば、加算と XOR は交換法則を満たすため、加算ハッシュと XOR ハッシュでは、内容が同じで順序だけ異なる文字列を区別できません。これはハッシュ衝突を悪化させ、一部の安全上の問題を引き起こす可能性があります。 -実際には、通常MD5、SHA-1、SHA-2、SHA-3などの標準ハッシュアルゴリズムを使用します。これらは任意の長さの入力データを固定長のハッシュ値にマッピングできます。 +実際には、MD5、SHA-1、SHA-2、SHA-3 などの標準的なハッシュアルゴリズムを用いることが一般的です。これらは任意長の入力データを、固定長のハッシュ値へ写像できます。 -過去1世紀にわたって、ハッシュアルゴリズムは継続的なアップグレードと最適化のプロセスにありました。一部の研究者はハッシュアルゴリズムの性能向上に努め、ハッカーを含む他の人々はハッシュアルゴリズムのセキュリティ問題を見つけることに専念しています。以下の表は、実用的なアプリケーションで一般的に使用されるハッシュアルゴリズムを示しています。 +ここ 1 世紀近くの間、ハッシュアルゴリズムは継続的に改良と最適化が進められてきました。ある研究者たちは性能向上に取り組み、別の研究者やハッカーたちは安全性の弱点を探し続けてきました。次の表は、実際の応用でよく使われるハッシュアルゴリズムを示したものです。 -- MD5とSHA-1は複数回攻撃に成功しており、さまざまなセキュリティアプリケーションで放棄されています。 -- SHA-2シリーズ、特にSHA-256は、現在最も安全なハッシュアルゴリズムの1つで、成功した攻撃は報告されておらず、さまざまなセキュリティアプリケーションとプロトコルで一般的に使用されています。 -- SHA-3はSHA-2と比較して実装コストが低く、計算効率が高いですが、現在の使用範囲はSHA-2シリーズほど広範囲ではありません。 +- MD5 と SHA-1 は何度も攻撃に成功されているため、各種のセキュリティ用途では廃止されています。 +- SHA-2 系列の SHA-256 は最も安全なハッシュアルゴリズムの 1 つであり、いまだに成功した攻撃例がないため、多くのセキュリティ用途やプロトコルで広く使われています。 +- SHA-3 は SHA-2 と比べて実装コストが低く、計算効率も高い一方で、現時点での普及度は SHA-2 系列に及びません。

  一般的なハッシュアルゴリズム

-| | MD5 | SHA-1 | SHA-2 | SHA-3 | -| --------------- | ----------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------- | ---------------------------- | -| リリース年 | 1992 | 1995 | 2002 | 2008 | -| 出力長 | 128 bit | 160 bit | 256/512 bit | 224/256/384/512 bit | -| ハッシュ衝突 | 頻繁 | 頻繁 | まれ | まれ | -| セキュリティレベル | 低、攻撃に成功している | 低、攻撃に成功している | 高 | 高 | -| アプリケーション | 放棄、データ整合性チェックにまだ使用 | 放棄 | 暗号通貨取引検証、デジタル署名など | SHA-2の代替として使用可能 | +| | MD5 | SHA-1 | SHA-2 | SHA-3 | +| -------- | ------------------------------ | ---------------- | ---------------------------- | ------------------- | +| 発表年 | 1992 | 1995 | 2002 | 2008 | +| 出力長 | 128 bit | 160 bit | 256/512 bit | 224/256/384/512 bit | +| ハッシュ衝突 | 多い | 多い | 非常に少ない | 非常に少ない | +| セキュリティレベル | 低く、攻撃に成功されている | 低く、攻撃に成功されている | 高い | 高い | +| 用途 | 廃止済みだが、データ完全性検査には使われる | 廃止済み | 暗号資産の取引検証、デジタル署名など | SHA-2 の代替に使える | -# データ構造におけるハッシュ値 +## データ構造のハッシュ値 -ハッシュ表のキーは整数、小数、文字列などのさまざまなデータ型にできることを知っています。プログラミング言語は通常、これらのデータ型に対して組み込みのハッシュアルゴリズムを提供し、ハッシュ表のバケットインデックスを計算します。Pythonを例に取ると、`hash()`関数を使用してさまざまなデータ型のハッシュ値を計算できます。 +ご存じのように、ハッシュテーブルの `key` には整数、小数、文字列などのデータ型を使えます。プログラミング言語は通常、これらのデータ型に対して組み込みのハッシュアルゴリズムを提供し、ハッシュテーブル内のバケットインデックス計算に利用します。Python を例にすると、`hash()` 関数を呼び出して各種データ型のハッシュ値を計算できます。 -- 整数とブール値のハッシュ値は、それら自身の値です。 -- 浮動小数点数と文字列のハッシュ値の計算はより複雑で、興味のある読者は自分で研究することをお勧めします。 -- タプルのハッシュ値は、その各要素のハッシュ値の組み合わせで、単一のハッシュ値になります。 -- オブジェクトのハッシュ値は、そのメモリアドレスに基づいて生成されます。オブジェクトのハッシュメソッドをオーバーライドすることで、内容に基づいてハッシュ値を生成できます。 +- 整数と真理値のハッシュ値は、その値自身です。 +- 浮動小数点数と文字列のハッシュ値の計算はやや複雑なので、興味がある読者は自分で調べてみてください。 +- タプルのハッシュ値は、各要素のハッシュ値を求めてから、それらを組み合わせて 1 つのハッシュ値にしたものです。 +- オブジェクトのハッシュ値は、そのメモリアドレスに基づいて生成されます。オブジェクトのハッシュメソッドをオーバーライドすれば、内容に基づくハッシュ値を実装できます。 !!! tip - 異なるプログラミング言語における組み込みハッシュ値計算関数の定義と方法は異なることに注意してください。 + 注意してください。組み込みのハッシュ値計算関数の定義や方法は、プログラミング言語ごとに異なります。 === "Python" ```python title="built_in_hash.py" num = 3 hash_num = hash(num) - # 整数3のハッシュ値は3 + # 整数 3 のハッシュ値は 3 bol = True hash_bol = hash(bol) - # ブール値Trueのハッシュ値は1 + # 真理値 True のハッシュ値は 1 dec = 3.14159 hash_dec = hash(dec) - # 小数3.14159のハッシュ値は326484311674566659 + # 小数 3.14159 のハッシュ値は 326484311674566659 - str = "Hello 算法" + str = "Hello アルゴリズム" hash_str = hash(str) - # 文字列"Hello 算法"のハッシュ値は4617003410720528961 + # 文字列「Hello アルゴリズム」のハッシュ値は 4617003410720528961 - tup = (12836, "小哈") + tup = (12836, "シャオハ") hash_tup = hash(tup) - # タプル(12836, '小哈')のハッシュ値は1029005403108185979 + # タプル (12836, 'シャオハ') のハッシュ値は 1029005403108185979 obj = ListNode(0) hash_obj = hash(obj) - # ListNodeオブジェクト0x1058fd810のハッシュ値は274267521 + # ノードオブジェクト のハッシュ値は 274267521 ``` === "C++" @@ -146,22 +146,22 @@ $$ ```cpp title="built_in_hash.cpp" int num = 3; size_t hashNum = hash()(num); - // 整数3のハッシュ値は3 + // 整数 3 のハッシュ値は 3 bool bol = true; size_t hashBol = hash()(bol); - // ブール値1のハッシュ値は1 + // 真理値 1 のハッシュ値は 1 double dec = 3.14159; size_t hashDec = hash()(dec); - // 小数3.14159のハッシュ値は4614256650576692846 + // 小数 3.14159 のハッシュ値は 4614256650576692846 - string str = "Hello 算法"; + string str = "Hello アルゴリズム"; size_t hashStr = hash()(str); - // 文字列"Hello 算法"のハッシュ値は15466937326284535026 + // 文字列「Hello アルゴリズム」のハッシュ値は 15466937326284535026 - // C++では、組み込みstd::hash()は基本データ型のハッシュ値のみを提供 - // 配列とオブジェクトのハッシュ値は別途実装が必要 + // C++ では、組み込みの std:hash() は基本データ型のハッシュ値計算のみを提供する + // 配列やオブジェクトのハッシュ値計算は自分で実装する必要がある ``` === "Java" @@ -169,27 +169,27 @@ $$ ```java title="built_in_hash.java" int num = 3; int hashNum = Integer.hashCode(num); - // 整数3のハッシュ値は3 + // 整数 3 のハッシュ値は 3 boolean bol = true; int hashBol = Boolean.hashCode(bol); - // ブール値trueのハッシュ値は1231 + // 真理値 true のハッシュ値は 1231 double dec = 3.14159; int hashDec = Double.hashCode(dec); - // 小数3.14159のハッシュ値は-1340954729 + // 小数 3.14159 のハッシュ値は -1340954729 - String str = "Hello 算法"; + String str = "Hello アルゴリズム"; int hashStr = str.hashCode(); - // 文字列"Hello 算法"のハッシュ値は-727081396 + // 文字列「Hello アルゴリズム」のハッシュ値は -727081396 - Object[] arr = { 12836, "小哈" }; + Object[] arr = { 12836, "シャオハ" }; int hashTup = Arrays.hashCode(arr); - // 配列[12836, 小哈]のハッシュ値は1151158 + // 配列 [12836, シャオハ] のハッシュ値は 1151158 ListNode obj = new ListNode(0); int hashObj = obj.hashCode(); - // ListNodeオブジェクトutils.ListNode@7dc5e7b4のハッシュ値は2110121908 + // ノードオブジェクト utils.ListNode@7dc5e7b4 のハッシュ値は 2110121908 ``` === "C#" @@ -197,33 +197,33 @@ $$ ```csharp title="built_in_hash.cs" int num = 3; int hashNum = num.GetHashCode(); - // 整数3のハッシュ値は3; + // 整数 3 のハッシュ値は 3; bool bol = true; int hashBol = bol.GetHashCode(); - // ブール値trueのハッシュ値は1; + // 真理値 true のハッシュ値は 1; double dec = 3.14159; int hashDec = dec.GetHashCode(); - // 小数3.14159のハッシュ値は-1340954729; + // 小数 3.14159 のハッシュ値は -1340954729; - string str = "Hello 算法"; + string str = "Hello アルゴリズム"; int hashStr = str.GetHashCode(); - // 文字列"Hello 算法"のハッシュ値は-586107568; + // 文字列「Hello アルゴリズム」のハッシュ値は -586107568; - object[] arr = [12836, "小哈"]; + object[] arr = [12836, "シャオハ"]; int hashTup = arr.GetHashCode(); - // 配列[12836, 小哈]のハッシュ値は42931033; + // 配列 [12836, シャオハ] のハッシュ値は 42931033; ListNode obj = new(0); int hashObj = obj.GetHashCode(); - // ListNodeオブジェクト0のハッシュ値は39053774; + // ノードオブジェクト 0 のハッシュ値は 39053774; ``` === "Go" ```go title="built_in_hash.go" - // Goには組み込みのハッシュコード関数が提供されていません + // Go は組み込みの hash code 関数を提供していない ``` === "Swift" @@ -231,39 +231,39 @@ $$ ```swift title="built_in_hash.swift" let num = 3 let hashNum = num.hashValue - // 整数3のハッシュ値は9047044699613009734 + // 整数 3 のハッシュ値は 9047044699613009734 let bol = true let hashBol = bol.hashValue - // ブール値trueのハッシュ値は-4431640247352757451 + // 真理値 true のハッシュ値は -4431640247352757451 let dec = 3.14159 let hashDec = dec.hashValue - // 小数3.14159のハッシュ値は-2465384235396674631 + // 小数 3.14159 のハッシュ値は -2465384235396674631 - let str = "Hello 算法" + let str = "Hello アルゴリズム" let hashStr = str.hashValue - // 文字列"Hello 算法"のハッシュ値は-7850626797806988787 + // 文字列「Hello アルゴリズム」のハッシュ値は -7850626797806988787 - let arr = [AnyHashable(12836), AnyHashable("小哈")] + let arr = [AnyHashable(12836), AnyHashable("シャオハ")] let hashTup = arr.hashValue - // 配列[AnyHashable(12836), AnyHashable("小哈")]のハッシュ値は-2308633508154532996 + // 配列 [AnyHashable(12836), AnyHashable("シャオハ")] のハッシュ値は -2308633508154532996 let obj = ListNode(x: 0) let hashObj = obj.hashValue - // ListNodeオブジェクトutils.ListNodeのハッシュ値は-2434780518035996159 + // ノードオブジェクト utils.ListNode のハッシュ値は -2434780518035996159 ``` === "JS" ```javascript title="built_in_hash.js" - // JavaScriptには組み込みのハッシュコード関数が提供されていません + // JavaScript は組み込みの hash code 関数を提供していない ``` === "TS" ```typescript title="built_in_hash.ts" - // TypeScriptには組み込みのハッシュコード関数が提供されていません + // TypeScript は組み込みの hash code 関数を提供していない ``` === "Dart" @@ -271,27 +271,27 @@ $$ ```dart title="built_in_hash.dart" int num = 3; int hashNum = num.hashCode; - // 整数3のハッシュ値は34803 + // 整数 3 のハッシュ値は 34803 bool bol = true; int hashBol = bol.hashCode; - // ブール値trueのハッシュ値は1231 + // 真理値 true のハッシュ値は 1231 double dec = 3.14159; int hashDec = dec.hashCode; - // 小数3.14159のハッシュ値は2570631074981783 + // 小数 3.14159 のハッシュ値は 2570631074981783 - String str = "Hello 算法"; + String str = "Hello アルゴリズム"; int hashStr = str.hashCode; - // 文字列"Hello 算法"のハッシュ値は468167534 + // 文字列「Hello アルゴリズム」のハッシュ値は 468167534 - List arr = [12836, "小哈"]; + List arr = [12836, "シャオハ"]; int hashArr = arr.hashCode; - // 配列[12836, 小哈]のハッシュ値は976512528 + // 配列 [12836, シャオハ] のハッシュ値は 976512528 ListNode obj = new ListNode(0); int hashObj = obj.hashCode; - // ListNodeオブジェクトInstance of 'ListNode'のハッシュ値は1033450432 + // ノードオブジェクト Instance of 'ListNode' のハッシュ値は 1033450432 ``` === "Rust" @@ -304,53 +304,107 @@ $$ let mut num_hasher = DefaultHasher::new(); num.hash(&mut num_hasher); let hash_num = num_hasher.finish(); - // 整数3のハッシュ値は568126464209439262 + // 整数 3 のハッシュ値は 568126464209439262 let bol = true; let mut bol_hasher = DefaultHasher::new(); bol.hash(&mut bol_hasher); let hash_bol = bol_hasher.finish(); - // ブール値trueのハッシュ値は4952851536318644461 + // 真理値 true のハッシュ値は 4952851536318644461 let dec: f32 = 3.14159; let mut dec_hasher = DefaultHasher::new(); dec.to_bits().hash(&mut dec_hasher); let hash_dec = dec_hasher.finish(); - // 小数3.14159のハッシュ値は2566941990314602357 + // 小数 3.14159 のハッシュ値は 2566941990314602357 - let str = "Hello 算法"; + let str = "Hello アルゴリズム"; let mut str_hasher = DefaultHasher::new(); str.hash(&mut str_hasher); let hash_str = str_hasher.finish(); - // 文字列"Hello 算法"のハッシュ値は16092673739211250988 + // 文字列「Hello アルゴリズム」のハッシュ値は 16092673739211250988 - let arr = (&12836, &"小哈"); + let arr = (&12836, &"シャオハ"); let mut tup_hasher = DefaultHasher::new(); arr.hash(&mut tup_hasher); let hash_tup = tup_hasher.finish(); - // タプル(12836, "小哈")のハッシュ値は1885128010422702749 + // タプル (12836, "シャオハ") のハッシュ値は 1885128010422702749 let node = ListNode::new(42); let mut hasher = DefaultHasher::new(); node.borrow().val.hash(&mut hasher); let hash = hasher.finish(); - // ListNodeオブジェクトRefCell { value: ListNode { val: 42, next: None } }のハッシュ値は15387811073369036852 + // ノードオブジェクト RefCell { value: ListNode { val: 42, next: None } } のハッシュ値は15387811073369036852 ``` === "C" ```c title="built_in_hash.c" - // Cには組み込みのハッシュコード関数が提供されていません + // C は組み込みの hash code 関数を提供していない ``` === "Kotlin" ```kotlin title="built_in_hash.kt" + val num = 3 + val hashNum = num.hashCode() + // 整数 3 のハッシュ値は 3 + val bol = true + val hashBol = bol.hashCode() + // 真理値 true のハッシュ値は 1231 + + val dec = 3.14159 + val hashDec = dec.hashCode() + // 小数 3.14159 のハッシュ値は -1340954729 + + val str = "Hello アルゴリズム" + val hashStr = str.hashCode() + // 文字列「Hello アルゴリズム」のハッシュ値は -727081396 + + val arr = arrayOf(12836, "シャオハ") + val hashTup = arr.hashCode() + // 配列 [12836, シャオハ] のハッシュ値は 189568618 + + val obj = ListNode(0) + val hashObj = obj.hashCode() + // ノードオブジェクト utils.ListNode@1d81eb93 のハッシュ値は 495053715 ``` -多くのプログラミング言語では、**不変オブジェクトのみがハッシュ表の`key`として機能できます**。リスト(動的配列)を`key`として使用する場合、リストの内容が変更されると、そのハッシュ値も変更され、ハッシュ表で元の`value`を見つけることができなくなります。 +=== "Ruby" -カスタムオブジェクト(連結リストノードなど)のメンバー変数は可変ですが、ハッシュ可能です。**これは、オブジェクトのハッシュ値が通常そのメモリアドレスに基づいて生成されるためです**。オブジェクトの内容が変更されても、メモリアドレスは同じままなので、ハッシュ値は変更されません。 + ```ruby title="built_in_hash.rb" + num = 3 + hash_num = num.hash + # 整数 3 のハッシュ値は -4385856518450339636 -異なるコンソールで出力されるハッシュ値が異なることに気づいたかもしれません。**これは、Pythonインタープリターが起動するたびに文字列ハッシュ関数にランダムソルトを追加するためです**。このアプローチはHashDoS攻撃を効果的に防ぎ、ハッシュアルゴリズムのセキュリティを向上させます。 + bol = true + hash_bol = bol.hash + # 真理値 true のハッシュ値は -1617938112149317027 + + dec = 3.14159 + hash_dec = dec.hash + # 小数 3.14159 のハッシュ値は -1479186995943067893 + + str = "Hello アルゴリズム" + hash_str = str.hash + # 文字列「Hello アルゴリズム」のハッシュ値は -4075943250025831763 + + tup = [12836, 'シャオハ'] + hash_tup = tup.hash + # タプル (12836, 'シャオハ') のハッシュ値は 1999544809202288822 + + obj = ListNode.new(0) + hash_obj = obj.hash + # ノードオブジェクト # のハッシュ値は 4302940560806366381 + ``` + +??? pythontutor "可視化実行" + + https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20num%20%3D%203%0A%20%20%20%20hash_num%20%3D%20hash%28num%29%0A%20%20%20%20%23%20%E6%95%B4%E6%95%B0%203%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%203%0A%0A%20%20%20%20bol%20%3D%20True%0A%20%20%20%20hash_bol%20%3D%20hash%28bol%29%0A%20%20%20%20%23%20%E5%B8%83%E5%B0%94%E9%87%8F%20True%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201%0A%0A%20%20%20%20dec%20%3D%203.14159%0A%20%20%20%20hash_dec%20%3D%20hash%28dec%29%0A%20%20%20%20%23%20%E5%B0%8F%E6%95%B0%203.14159%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20326484311674566659%0A%0A%20%20%20%20str%20%3D%20%22Hello%20%E7%AE%97%E6%B3%95%22%0A%20%20%20%20hash_str%20%3D%20hash%28str%29%0A%20%20%20%20%23%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E2%80%9CHello%20%E7%AE%97%E6%B3%95%E2%80%9D%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%204617003410720528961%0A%0A%20%20%20%20tup%20%3D%20%2812836,%20%22%E5%B0%8F%E5%93%88%22%29%0A%20%20%20%20hash_tup%20%3D%20hash%28tup%29%0A%20%20%20%20%23%20%E5%85%83%E7%BB%84%20%2812836,%20'%E5%B0%8F%E5%93%88'%29%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%201029005403108185979%0A%0A%20%20%20%20obj%20%3D%20ListNode%280%29%0A%20%20%20%20hash_obj%20%3D%20hash%28obj%29%0A%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%AF%B9%E8%B1%A1%20%3CListNode%20object%20at%200x1058fd810%3E%20%E7%9A%84%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%BA%20274267521&cumulative=false&curInstr=19&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +多くのプログラミング言語では、**不変オブジェクトだけがハッシュテーブルの `key` として使えます**。仮にリスト(動的配列)を `key` とすると、その内容が変化したときにハッシュ値も変わってしまうため、もとの `value` をハッシュテーブルから検索できなくなります。 + +カスタムオブジェクト(たとえば連結リストのノード)のメンバ変数は可変ですが、それでもハッシュ可能です。**これは、オブジェクトのハッシュ値が通常はメモリアドレスに基づいて生成されるためです**。オブジェクトの内容が変化しても、メモリアドレスが変わらなければ、ハッシュ値も変わりません。 + +注意深い人なら、異なるコンソールでプログラムを実行したときに、出力されるハッシュ値が異なることに気づくかもしれません。**これは、Python インタプリタが起動のたびに文字列ハッシュ関数へランダムな salt 値を追加しているためです**。この方法によって HashDoS 攻撃を効果的に防ぎ、ハッシュアルゴリズムの安全性を高めています。 diff --git a/ja/docs/chapter_hashing/hash_collision.md b/ja/docs/chapter_hashing/hash_collision.md index 4bfc3d57a..823c23e79 100644 --- a/ja/docs/chapter_hashing/hash_collision.md +++ b/ja/docs/chapter_hashing/hash_collision.md @@ -1,108 +1,108 @@ # ハッシュ衝突 -前節で述べたように、**ほとんどの場合、ハッシュ関数の入力空間は出力空間よりもはるかに大きい**ため、理論的にはハッシュ衝突は避けられません。例えば、入力空間がすべての整数で、出力空間が配列容量のサイズの場合、複数の整数が必然的に同じバケットインデックスにマッピングされます。 +前節で述べたように、**通常、ハッシュ関数の入力空間は出力空間よりもはるかに大きい**ため、理論上ハッシュ衝突は避けられません。例えば、入力空間がすべての整数で、出力空間が配列の容量サイズである場合、必然的に複数の整数が同じバケットインデックスに写像されます。 -ハッシュ衝突は誤ったクエリ結果につながり、ハッシュ表の使いやすさに深刻な影響を与える可能性があります。この問題に対処するために、ハッシュ衝突が発生するたびに、衝突が消えるまでハッシュ表のリサイズを実行します。このアプローチは非常にシンプルで直接的であり、うまく機能します。しかし、テーブルの拡張には大量のデータ移行とハッシュコードの再計算が含まれ、これらは高コストであるため、非常に非効率的に見えます。効率を向上させるために、以下の戦略を採用できます: +ハッシュ衝突は検索結果の誤りを招き、ハッシュテーブルの利用可能性に深刻な影響を与えます。この問題を解決するために、ハッシュ衝突が発生するたびにハッシュテーブルを拡張し、衝突が消えるまで続けることが考えられます。この方法は単純で効果的ですが、効率が低すぎます。なぜなら、ハッシュテーブルの拡張には大量のデータ移動とハッシュ値の計算が必要だからです。効率を高めるために、次の戦略を採用できます。 -1. **ハッシュ衝突が発生した場合でも、ターゲット要素の検索が適切に機能する**ようにハッシュ表のデータ構造を改善する。 -2. 深刻な衝突が観察され、必要になる前に、拡張は最後の手段とする。 +1. ハッシュテーブルのデータ構造を改良し、**ハッシュ衝突が発生してもハッシュテーブルが正常に動作できるようにする**。 +2. 必要な場合、すなわちハッシュ衝突が比較的深刻なときにのみ、拡張操作を実行する。 -ハッシュ表の構造を改善する主な方法は2つあります:「連鎖法」と「オープンアドレス法」です。 +ハッシュテーブルの構造改善方法には、主に「チェイン法」と「オープンアドレッシング」があります。 -## 連鎖法 +## チェイン法 -元のハッシュ表では、各バケットは1つのキー値ペアのみを格納できます。連鎖法は単一の要素を連結リストに変換し、キー値ペアをリストノードとして扱い、衝突するすべてのキー値ペアを同じ連結リストに格納します。下図は連鎖法を使用したハッシュ表の例を示しています。 +元のハッシュテーブルでは、各バケットには 1 つのキーと値のペアしか格納できません。チェイン法(separate chaining)では、単一要素を連結リストに置き換え、キーと値のペアを連結リストのノードとして扱い、衝突したすべてのキーと値のペアを同じ連結リストに格納します。下図はチェイン法によるハッシュテーブルの例を示しています。 -![連鎖法ハッシュ表](hash_collision.assets/hash_table_chaining.png) +![チェイン法ハッシュテーブル](hash_collision.assets/hash_table_chaining.png) -連鎖法で実装されたハッシュ表の操作は以下のように変更されます: +チェイン法で実装されたハッシュテーブルでは、操作方法が次のように変わります。 -- **要素のクエリ**: `key`を入力し、ハッシュ関数を通してバケットインデックスを取得し、連結リストのヘッドノードにアクセスします。連結リストを走査してキーを比較し、ターゲットキー値ペアを見つけます。 -- **要素の追加**: ハッシュ関数を通して連結リストのヘッドノードにアクセスし、ノード(キー値ペア)をリストに追加します。 -- **要素の削除**: ハッシュ関数の結果に基づいて連結リストのヘッドにアクセスし、連結リストを走査してターゲットノードを見つけて削除します。 +- **要素の検索**:入力 `key` をハッシュ関数に通してバケットインデックスを得ると、連結リストの先頭ノードにアクセスできます。その後、連結リストを走査して `key` を比較し、目的のキーと値のペアを探します。 +- **要素の追加**:まずハッシュ関数で連結リストの先頭ノードにアクセスし、その後ノード(キーと値のペア)を連結リストに追加します。 +- **要素の削除**:ハッシュ関数の結果に基づいて連結リストの先頭にアクセスし、続いて連結リストを走査して対象ノードを探し、削除します。 -連鎖法には以下の制限があります: +チェイン法には次の制約があります。 -- **空間使用量の増加**: 連結リストにはノードポインタが含まれており、配列よりも多くのメモリ空間を消費します。 -- **クエリ効率の低下**: 対応する要素を見つけるために連結リストの線形走査が必要になるためです。 +- **使用メモリの増加**:連結リストにはノードポインタが含まれるため、配列よりも多くのメモリを消費します。 +- **検索効率の低下**:対応する要素を見つけるために連結リストを線形走査する必要があるためです。 -以下のコードは連鎖法ハッシュ表の簡単な実装を提供し、注意すべき2つの点があります: +以下のコードはチェイン法ハッシュテーブルの簡単な実装を示しています。注意すべき点は 2 つあります。 -- 簡単にするために、連結リストの代わりにリスト(動的配列)を使用します。この設定では、ハッシュ表(配列)は複数のバケットを含み、各バケットはリストです。 -- この実装にはハッシュ表のリサイズメソッドが含まれています。負荷率が$\frac{2}{3}$を超えると、ハッシュ表を元のサイズの2倍に拡張します。 +- 連結リストの代わりにリスト(動的配列)を使って、コードを簡潔にしています。この設定では、ハッシュテーブル(配列)は複数のバケットを含み、各バケットは 1 つのリストです。 +- 以下の実装にはハッシュテーブルの拡張メソッドが含まれています。負荷率が $\frac{2}{3}$ を超えたとき、ハッシュテーブルを元の $2$ 倍に拡張します。 ```src [file]{hash_map_chaining}-[class]{hash_map_chaining}-[func]{} ``` -連結リストが非常に長い場合、クエリ効率$O(n)$が悪いことは注目に値します。**この場合、リストを「AVL木」または「赤黒木」に変換して**、クエリ操作の時間計算量を$O(\log n)$に最適化できます。 +注意すべきなのは、連結リストが長い場合、検索効率 $O(n)$ は非常に低いことです。**このとき、連結リストを「AVL 木」または「赤黒木」に変換することで**、検索操作の時間計算量を $O(\log n)$ に最適化できます。 -## オープンアドレス法 +## オープンアドレッシング -オープンアドレス法は追加のデータ構造を導入せず、代わりに「複数回プローブ」を通してハッシュ衝突を処理します。プローブ方法には主に線形プローブ、二次プローブ、二重ハッシュがあります。 +オープンアドレッシング(open addressing)では追加のデータ構造を導入せず、「複数回の探索」によってハッシュ衝突を処理します。探索方法には主に線形探索、二次探索、多重ハッシュなどがあります。 -線形プローブを例にして、オープンアドレス法ハッシュ表のメカニズムを紹介しましょう。 +以下では線形探索を例に、オープンアドレッシングハッシュテーブルの動作の仕組みを説明します。 -### 線形プローブ +### 線形探索 -線形プローブは固定ステップの線形検索をプローブに使用し、通常のハッシュ表とは異なります。 +線形探索では、固定ステップ長の線形探索によって探索を行います。その操作方法は通常のハッシュテーブルとは異なります。 -- **要素の挿入**: ハッシュ関数を使用してバケットインデックスを計算します。バケットに既に要素が含まれている場合、衝突位置から線形に前方に走査し(通常ステップサイズは$1$)、空のバケットが見つかるまで進み、要素を挿入します。 -- **要素の検索**: ハッシュ衝突に遭遇した場合、同じステップサイズを使用して線形に前方に走査し、対応する要素が見つかったら`value`を返します。空のバケットに遭遇した場合、ターゲット要素がハッシュ表にないことを意味するため、`None`を返します。 +- **要素の挿入**:ハッシュ関数によってバケットインデックスを計算し、バケット内にすでに要素がある場合は、衝突位置から後方へ線形に走査し(ステップ長は通常 $1$ )、空のバケットが見つかるまで進み、その中に要素を挿入します。 +- **要素の検索**:ハッシュ衝突が見つかった場合は、同じステップ長で後方へ線形走査を行い、対応する要素が見つかるまで続け、 `value` を返します。空のバケットに遭遇した場合は、対象要素がハッシュテーブル内に存在しないことを意味するため、 `None` を返します。 -下図はオープンアドレス法(線形プローブ)ハッシュ表におけるキー値ペアの分布を示しています。このハッシュ関数によると、下二桁が同じキーは同じバケットにマッピングされます。線形プローブを通して、それらはそのバケットとその下のバケットに順次格納されます。 +下図はオープンアドレッシング(線形探索)ハッシュテーブルにおけるキーと値のペアの分布を示しています。このハッシュ関数では、末尾 2 桁が同じ `key` はすべて同じバケットに写像されます。線形探索によって、それらはそのバケットとその後続のバケットに順に格納されます。 -![オープンアドレス法(線形プローブ)ハッシュ表におけるキー値ペアの分布](hash_collision.assets/hash_table_linear_probing.png) +![オープンアドレッシング(線形探索)ハッシュテーブルにおけるキーと値のペアの分布](hash_collision.assets/hash_table_linear_probing.png) -しかし、**線形プローブは「クラスタリング」を作りやすい傾向があります**。具体的には、配列内の連続的に占有された位置が長いほど、これらの連続した位置でハッシュ衝突が発生する確率が高くなり、その位置でのクラスタリングの成長をさらに促進し、悪循環を形成し、最終的に挿入、削除、クエリ、更新操作の効率低下につながります。 +しかし、**線形探索では「クラスタリング現象」が起こりやすい**です。具体的には、配列内で連続して占有された位置が長いほど、それらの連続位置でハッシュ衝突が発生する可能性が高くなり、さらにその位置の集積成長を促して悪循環を生み、最終的には追加・削除・検索・更新操作の効率低下を招きます。 -**オープンアドレス法ハッシュ表では要素を直接削除できない**ことに注意することが重要です。要素を削除すると、配列に空のバケット`None`が作成されます。要素を検索する際、線形プローブがこの空のバケットに遭遇すると戻ってしまい、このバケットの下の要素にアクセスできなくなります。プログラムはこれらの要素が存在しないと誤って仮定する可能性があります。下図に示すとおりです。 +注意すべきなのは、**オープンアドレッシングハッシュテーブルでは要素を直接削除できない**ことです。これは、要素を削除すると配列内に空バケット `None` が生じ、要素を検索するときに線形探索がその空バケットに到達した時点で返ってしまうため、その空バケットより後ろの要素には二度とアクセスできなくなるからです。結果として、プログラムがそれらの要素を存在しないと誤判定する可能性があります。下図のとおりです。 -![オープンアドレス法での削除によるクエリ問題](hash_collision.assets/hash_table_open_addressing_deletion.png) +![オープンアドレッシングで要素を削除したことによる検索問題](hash_collision.assets/hash_table_open_addressing_deletion.png) -この問題を解決するために、遅延削除メカニズムを採用できます:ハッシュ表から要素を直接削除する代わりに、**定数`TOMBSTONE`を使用してバケットをマークします**。このメカニズムでは、`None`と`TOMBSTONE`の両方が空のバケットを表し、キー値ペアを保持できます。ただし、線形プローブが`TOMBSTONE`に遭遇した場合、その下にまだキー値ペアがある可能性があるため、走査を続ける必要があります。 +この問題を解決するために、遅延削除(lazy deletion)の仕組みを採用できます。これは要素をハッシュテーブルから直接取り除かず、**代わりに定数 `TOMBSTONE` を使ってこのバケットをマークします**。この仕組みでは、`None` と `TOMBSTONE` はどちらも空バケットを表し、どちらにもキーと値のペアを配置できます。ただし異なるのは、線形探索が `TOMBSTONE` に到達した場合は、その先にキーと値のペアが存在する可能性があるため、探索を続けるべきだという点です。 -しかし、**遅延削除はハッシュ表の性能劣化を加速する可能性があります**。削除操作のたびに削除マークが生成され、`TOMBSTONE`が増加すると、線形プローブがターゲット要素を見つけるために複数の`TOMBSTONE`をスキップする必要がある可能性があるため、検索時間も増加します。 +しかし、**遅延削除はハッシュテーブルの性能劣化を加速させる可能性があります**。これは、削除操作のたびに削除マークが 1 つ生成され、`TOMBSTONE` が増えるにつれて探索時間も増加するためです。線形探索では、対象要素を見つけるまでに複数の `TOMBSTONE` を飛び越える必要があるかもしれません。 -これに対処するために、線形プローブ中に最初に遭遇した`TOMBSTONE`のインデックスを記録し、検索されたターゲット要素とその`TOMBSTONE`の位置を交換することを検討してください。これを行う利点は、要素がクエリまたは追加されるたびに、要素がその理想的な位置(プローブの開始点)により近いバケットに移動され、クエリ効率が最適化されることです。 +そのため、線形探索では、遭遇した最初の `TOMBSTONE` のインデックスを記録し、見つかった対象要素とその `TOMBSTONE` を交換することを考えます。こうする利点は、要素を検索または追加するたびに、要素が理想位置(探索開始点)により近いバケットへ移動し、検索効率が向上することです。 -以下のコードは、遅延削除を使用したオープンアドレス法(線形プローブ)ハッシュ表を実装しています。ハッシュ表の空間をより有効に活用するために、ハッシュ表を「循環配列」として扱います。配列の終わりを超えると、最初に戻って走査を続けます。 +以下のコードは、遅延削除を含むオープンアドレッシング(線形探索)ハッシュテーブルを実装したものです。ハッシュテーブルの空間をより十分に活用するために、ハッシュテーブルを「環状配列」とみなし、配列末尾を越えたら先頭に戻って探索を続けます。 ```src [file]{hash_map_open_addressing}-[class]{hash_map_open_addressing}-[func]{} ``` -### 二次プローブ +### 二次探索 -二次プローブは線形プローブに似ており、オープンアドレス法の一般的な戦略の1つです。衝突が発生した場合、二次プローブは単純に固定ステップ数をスキップするのではなく、「プローブ回数の二乗」に等しいステップ数、つまり$1, 4, 9, \dots$ステップをスキップします。 +二次探索は線形探索に似ており、オープンアドレッシングの一般的な戦略の 1 つです。衝突が発生したとき、二次探索では単純に固定歩数を飛ばすのではなく、「探索回数の二乗」に相当する歩数、すなわち $1, 4, 9, \dots$ 歩を飛ばします。 -二次プローブには以下の利点があります: +二次探索には主に次の利点があります。 -- 二次プローブは、プローブ回数の二乗の距離をスキップすることで、線形プローブのクラスタリング効果を軽減しようとします。 -- 二次プローブはより大きな距離をスキップして空の位置を見つけ、データをより均等に分散するのに役立ちます。 +- 二次探索は、探索回数の二乗の距離を飛ばすことで、線形探索のクラスタリング効果を緩和しようとします。 +- 二次探索はより大きな距離を飛ばして空き位置を探すため、データ分布がより均一になるのに役立ちます。 -しかし、二次プローブは完璧ではありません: +しかし、二次探索は完璧ではありません。 -- クラスタリングは依然として存在し、つまり一部の位置は他の位置よりも占有される可能性が高いです。 -- 二乗の成長により、二次プローブはハッシュ表全体をプローブできない可能性があり、ハッシュ表に空のバケットがあっても、二次プローブがアクセスできない可能性があります。 +- 依然としてクラスタリング現象は存在し、ある位置が他の位置より占有されやすいことがあります。 +- 二乗の増加により、二次探索はハッシュテーブル全体を探索できない可能性があります。これは、ハッシュテーブルに空バケットがあっても、二次探索ではそこに到達できないことがあることを意味します。 -### 二重ハッシュ +### 多重ハッシュ -名前が示すように、二重ハッシュ法は複数のハッシュ関数$f_1(x)$、$f_2(x)$、$f_3(x)$、$\dots$をプローブに使用します。 +その名のとおり、多重ハッシュ法では複数のハッシュ関数 $f_1(x)$、$f_2(x)$、$f_3(x)$、$\dots$ を使って探索を行います。 -- **要素の挿入**: ハッシュ関数$f_1(x)$が衝突に遭遇した場合、$f_2(x)$を試し、以下同様に、空の位置が見つかって要素が挿入されるまで続けます。 -- **要素の検索**: 同じハッシュ関数の順序で検索し、ターゲット要素が見つかって返されるまで、または空の位置に遭遇するかすべてのハッシュ関数が試されるまで続け、要素がハッシュ表にないことを示し、`None`を返します。 +- **要素の挿入**:ハッシュ関数 $f_1(x)$ で衝突が発生した場合は、$f_2(x)$ を試し、以下同様に、空き位置が見つかるまで続けてから要素を挿入します。 +- **要素の検索**:同じハッシュ関数の順序で探索し、対象要素が見つかった時点で返します。空き位置に遭遇するか、すべてのハッシュ関数を試しても見つからない場合は、ハッシュテーブル内にその要素は存在しないため、 `None` を返します。 -線形プローブと比較して、二重ハッシュ法はクラスタリングが起こりにくいですが、複数のハッシュ関数は追加の計算オーバーヘッドを導入します。 +線形探索と比べると、多重ハッシュ法はクラスタリングを起こしにくい一方で、複数のハッシュ関数により追加の計算量が発生します。 !!! tip - オープンアドレス法(線形プローブ、二次プローブ、二重ハッシュ)ハッシュ表はすべて「要素を直接削除できない」という問題があることに注意してください。 + 注意してください。オープンアドレッシング(線形探索、二次探索、多重ハッシュ)のハッシュテーブルには、いずれも「要素を直接削除できない」という問題があります。 ## プログラミング言語の選択 -異なるプログラミング言語は異なるハッシュ表実装戦略を採用しています。以下にいくつかの例を示します: +各種プログラミング言語は異なるハッシュテーブル実装戦略を採用しています。以下にいくつか例を挙げます。 -- Pythonはオープンアドレス法を使用します。`dict`辞書はプローブに疑似乱数を使用します。 -- Javaは連鎖法を使用します。JDK 1.8以降、`HashMap`の配列長が64に達し、連結リストの長さが8に達すると、連結リストは検索性能を向上させるために赤黒木に変換されます。 -- Goは連鎖法を使用します。Goは各バケットが最大8つのキー値ペアを格納できることを規定し、容量を超えた場合はオーバーフローバケットが連結されます。オーバーフローバケットが多すぎる場合、性能を確保するために特別な等容量リサイズ操作が実行されます。 +- Python はオープンアドレッシングを採用しています。辞書 `dict` は疑似乱数を用いて探索します。 +- Java はチェイン法を採用しています。JDK 1.8 以降、`HashMap` 内の配列長が 64 に達し、かつ連結リスト長が 8 に達すると、連結リストは検索性能を高めるため赤黒木に変換されます。 +- Go はチェイン法を採用しています。Go では各バケットに最大 8 個のキーと値のペアを格納でき、容量を超えるとオーバーフローバケットを連結します。オーバーフローバケットが多すぎる場合は、性能を確保するために特殊な等量拡張操作を実行します。 diff --git a/ja/docs/chapter_hashing/hash_map.md b/ja/docs/chapter_hashing/hash_map.md index 16db87927..ff90321ca 100644 --- a/ja/docs/chapter_hashing/hash_map.md +++ b/ja/docs/chapter_hashing/hash_map.md @@ -1,238 +1,238 @@ -# ハッシュ表 +# ハッシュテーブル -ハッシュ表ハッシュマップとも呼ばれ、キーと値の間のマッピングを確立し、効率的な要素の取得を可能にするデータ構造です。具体的には、ハッシュ表に`key`を入力すると、$O(1)$の時間計算量で対応する`value`を取得できます。 +ハッシュテーブル(hash table)は、散列表とも呼ばれ、キー `key` と値 `value` の対応関係を構築することで、高効率な要素検索を実現します。具体的には、ハッシュテーブルにキー `key` を入力すると、対応する値 `value` を $O(1)$ 時間で取得できます。 -下図に示すように、$n$人の学生がいて、各学生には「名前」と「学籍番号」の2つのデータフィールドがあるとします。学籍番号を入力として対応する名前を返すクエリ機能を実装したい場合、下図に示すハッシュ表を使用できます。 +以下の図に示すように、$n$ 人の学生がいるとし、各学生は「名前」と「学籍番号」の 2 つの情報を持っています。もし「学籍番号を入力すると対応する名前を返す」という検索機能を実現したいなら、下図のようなハッシュテーブルを用いることができます。 -![ハッシュ表の抽象的な表現](hash_map.assets/hash_table_lookup.png) +![ハッシュテーブルの抽象表現](hash_map.assets/hash_table_lookup.png) -ハッシュ表に加えて、配列や連結リストもクエリ機能の実装に使用できますが、時間計算量が異なります。効率は以下の表で比較されています: +ハッシュテーブルのほかに、配列や連結リストでも検索機能を実現できます。それらの効率比較を次の表に示します。 -- **要素の挿入**: 配列(または連結リスト)の末尾に要素を追加するだけです。この操作の時間計算量は$O(1)$です。 -- **要素の検索**: 配列(または連結リスト)がソートされていないため、要素を検索するにはすべての要素を走査する必要があります。この操作の時間計算量は$O(n)$です。 -- **要素の削除**: 要素を削除するには、まずその要素を見つけてから、配列(または連結リスト)から削除します。この操作の時間計算量は$O(n)$です。 +- **要素の追加**:要素を配列(連結リスト)の末尾に追加するだけでよく、$O(1)$ 時間です。 +- **要素の検索**:配列(連結リスト)は無秩序なので、すべての要素を走査する必要があり、$O(n)$ 時間かかります。 +- **要素の削除**:先に要素を検索してから配列(連結リスト)から削除する必要があり、$O(n)$ 時間かかります。 -

  一般的な操作の時間効率の比較

+

  要素検索効率の比較

-| | 配列 | 連結リスト | ハッシュ表 | -| -------------- | ------ | ----------- | ---------- | -| 要素の検索 | $O(n)$ | $O(n)$ | $O(1)$ | -| 要素の挿入 | $O(1)$ | $O(1)$ | $O(1)$ | -| 要素の削除 | $O(n)$ | $O(n)$ | $O(1)$ | +| | 配列 | 連結リスト | ハッシュテーブル | +| -------- | ------ | ------ | ------ | +| 要素の検索 | $O(n)$ | $O(n)$ | $O(1)$ | +| 要素の追加 | $O(1)$ | $O(1)$ | $O(1)$ | +| 要素の削除 | $O(n)$ | $O(n)$ | $O(1)$ | -観察されるように、**ハッシュ表における操作(挿入、削除、検索、変更)の時間計算量は$O(1)$**で、非常に効率的です。 +以上から分かるように、**ハッシュテーブルにおける追加・削除・検索・更新の時間計算量はいずれも $O(1)$** であり、非常に高効率です。 -## ハッシュ表の一般的な操作 +## ハッシュテーブルの基本操作 -ハッシュ表の一般的な操作には、初期化、クエリ、キー値ペアの追加、キー値ペアの削除があります。以下はコード例です: +ハッシュテーブルの一般的な操作には、初期化、検索、キーと値のペアの追加、キーと値のペアの削除などがあります。コード例は以下のとおりです: === "Python" ```python title="hash_map.py" - # ハッシュ表を初期化 + # ハッシュテーブルを初期化 hmap: dict = {} # 追加操作 - # ハッシュ表にキー値ペア (key, value) を追加 - hmap[12836] = "小哈" - hmap[15937] = "小啰" - hmap[16750] = "小算" - hmap[13276] = "小法" - hmap[10583] = "小鸭" + # ハッシュテーブルにキーと値のペア (key, value) を追加 + hmap[12836] = "シャオハ" + hmap[15937] = "シャオルオ" + hmap[16750] = "シャオスワン" + hmap[13276] = "シャオファ" + hmap[10583] = "シャオヤー" - # クエリ操作 - # ハッシュ表にキーを入力し、値を取得 + # 検索操作 + # ハッシュテーブルにキー key を入力し、値 value を取得 name: str = hmap[15937] # 削除操作 - # ハッシュ表からキー値ペア (key, value) を削除 + # ハッシュテーブルからキーと値のペア (key, value) を削除 hmap.pop(10583) ``` === "C++" ```cpp title="hash_map.cpp" - /* ハッシュ表を初期化 */ + /* ハッシュテーブルを初期化 */ unordered_map map; /* 追加操作 */ - // ハッシュ表にキー値ペア (key, value) を追加 - map[12836] = "小哈"; - map[15937] = "小啰"; - map[16750] = "小算"; - map[13276] = "小法"; - map[10583] = "小鸭"; + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map[12836] = "シャオハ"; + map[15937] = "シャオルオ"; + map[16750] = "シャオスワン"; + map[13276] = "シャオファ"; + map[10583] = "シャオヤー"; - /* クエリ操作 */ - // ハッシュ表にキーを入力し、値を取得 + /* 検索操作 */ + // ハッシュテーブルにキー key を入力し、値 value を取得 string name = map[15937]; /* 削除操作 */ - // ハッシュ表からキー値ペア (key, value) を削除 + // ハッシュテーブルからキーと値のペア (key, value) を削除 map.erase(10583); ``` === "Java" ```java title="hash_map.java" - /* ハッシュ表を初期化 */ + /* ハッシュテーブルを初期化 */ Map map = new HashMap<>(); /* 追加操作 */ - // ハッシュ表にキー値ペア (key, value) を追加 - map.put(12836, "小哈"); - map.put(15937, "小啰"); - map.put(16750, "小算"); - map.put(13276, "小法"); - map.put(10583, "小鸭"); + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.put(12836, "シャオハ"); + map.put(15937, "シャオルオ"); + map.put(16750, "シャオスワン"); + map.put(13276, "シャオファ"); + map.put(10583, "シャオヤー"); - /* クエリ操作 */ - // ハッシュ表にキーを入力し、値を取得 + /* 検索操作 */ + // ハッシュテーブルにキー key を入力し、値 value を取得 String name = map.get(15937); /* 削除操作 */ - // ハッシュ表からキー値ペア (key, value) を削除 + // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(10583); ``` === "C#" ```csharp title="hash_map.cs" - /* ハッシュ表を初期化 */ + /* ハッシュテーブルを初期化 */ Dictionary map = new() { /* 追加操作 */ - // ハッシュ表にキー値ペア (key, value) を追加 - { 12836, "小哈" }, - { 15937, "小啰" }, - { 16750, "小算" }, - { 13276, "小法" }, - { 10583, "小鸭" } + // ハッシュテーブルにキーと値のペア (key, value) を追加 + { 12836, "シャオハ" }, + { 15937, "シャオルオ" }, + { 16750, "シャオスワン" }, + { 13276, "シャオファ" }, + { 10583, "シャオヤー" } }; - /* クエリ操作 */ - // ハッシュ表にキーを入力し、値を取得 + /* 検索操作 */ + // ハッシュテーブルにキー key を入力し、値 value を取得 string name = map[15937]; /* 削除操作 */ - // ハッシュ表からキー値ペア (key, value) を削除 + // ハッシュテーブルからキーと値のペア (key, value) を削除 map.Remove(10583); ``` === "Go" ```go title="hash_map_test.go" - /* ハッシュ表を初期化 */ + /* ハッシュテーブルを初期化 */ hmap := make(map[int]string) /* 追加操作 */ - // ハッシュ表にキー値ペア (key, value) を追加 - hmap[12836] = "小哈" - hmap[15937] = "小啰" - hmap[16750] = "小算" - hmap[13276] = "小法" - hmap[10583] = "小鸭" + // ハッシュテーブルにキーと値のペア (key, value) を追加 + hmap[12836] = "シャオハ" + hmap[15937] = "シャオルオ" + hmap[16750] = "シャオスワン" + hmap[13276] = "シャオファ" + hmap[10583] = "シャオヤー" - /* クエリ操作 */ - // ハッシュ表にキーを入力し、値を取得 + /* 検索操作 */ + // ハッシュテーブルにキー key を入力し、値 value を取得 name := hmap[15937] /* 削除操作 */ - // ハッシュ表からキー値ペア (key, value) を削除 + // ハッシュテーブルからキーと値のペア (key, value) を削除 delete(hmap, 10583) ``` === "Swift" ```swift title="hash_map.swift" - /* ハッシュ表を初期化 */ + /* ハッシュテーブルを初期化 */ var map: [Int: String] = [:] /* 追加操作 */ - // ハッシュ表にキー値ペア (key, value) を追加 - map[12836] = "小哈" - map[15937] = "小啰" - map[16750] = "小算" - map[13276] = "小法" - map[10583] = "小鸭" + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map[12836] = "シャオハ" + map[15937] = "シャオルオ" + map[16750] = "シャオスワン" + map[13276] = "シャオファ" + map[10583] = "シャオヤー" - /* クエリ操作 */ - // ハッシュ表にキーを入力し、値を取得 + /* 検索操作 */ + // ハッシュテーブルにキー key を入力し、値 value を取得 let name = map[15937]! /* 削除操作 */ - // ハッシュ表からキー値ペア (key, value) を削除 + // ハッシュテーブルからキーと値のペア (key, value) を削除 map.removeValue(forKey: 10583) ``` === "JS" ```javascript title="hash_map.js" - /* ハッシュ表を初期化 */ + /* ハッシュテーブルを初期化 */ const map = new Map(); /* 追加操作 */ - // ハッシュ表にキー値ペア (key, value) を追加 - map.set(12836, '小哈'); - map.set(15937, '小啰'); - map.set(16750, '小算'); - map.set(13276, '小法'); - map.set(10583, '小鸭'); + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.set(12836, 'シャオハ'); + map.set(15937, 'シャオルオ'); + map.set(16750, 'シャオスワン'); + map.set(13276, 'シャオファ'); + map.set(10583, 'シャオヤー'); - /* クエリ操作 */ - // ハッシュ表にキーを入力し、値を取得 + /* 検索操作 */ + // ハッシュテーブルにキー key を入力し、値 value を取得 let name = map.get(15937); /* 削除操作 */ - // ハッシュ表からキー値ペア (key, value) を削除 + // ハッシュテーブルからキーと値のペア (key, value) を削除 map.delete(10583); ``` === "TS" ```typescript title="hash_map.ts" - /* ハッシュ表を初期化 */ + /* ハッシュテーブルを初期化 */ const map = new Map(); /* 追加操作 */ - // ハッシュ表にキー値ペア (key, value) を追加 - map.set(12836, '小哈'); - map.set(15937, '小啰'); - map.set(16750, '小算'); - map.set(13276, '小法'); - map.set(10583, '小鸭'); - console.info('\n追加後、ハッシュ表は\nKey -> Value'); + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.set(12836, 'シャオハ'); + map.set(15937, 'シャオルオ'); + map.set(16750, 'シャオスワン'); + map.set(13276, 'シャオファ'); + map.set(10583, 'シャオヤー'); + console.info('\n追加後のハッシュテーブルは次のとおりです\nKey -> Value'); console.info(map); - /* クエリ操作 */ - // ハッシュ表にキーを入力し、値を取得 + /* 検索操作 */ + // ハッシュテーブルにキー key を入力し、値 value を取得 let name = map.get(15937); - console.info('\n学籍番号15937を入力、名前を問い合わせ ' + name); + console.info('\n学籍番号 15937 を入力し、名前を検索: ' + name); /* 削除操作 */ - // ハッシュ表からキー値ペア (key, value) を削除 + // ハッシュテーブルからキーと値のペア (key, value) を削除 map.delete(10583); - console.info('\n10583を削除後、ハッシュ表は\nKey -> Value'); + console.info('\n10583 を削除した後のハッシュテーブル\nKey -> Value'); console.info(map); ``` === "Dart" ```dart title="hash_map.dart" - /* ハッシュ表を初期化 */ + /* ハッシュテーブルを初期化 */ Map map = {}; /* 追加操作 */ - // ハッシュ表にキー値ペア (key, value) を追加 - map[12836] = "小哈"; - map[15937] = "小啰"; - map[16750] = "小算"; - map[13276] = "小法"; - map[10583] = "小鸭"; + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map[12836] = "シャオハ"; + map[15937] = "シャオルオ"; + map[16750] = "シャオスワン"; + map[13276] = "シャオファ"; + map[10583] = "シャオヤー"; - /* クエリ操作 */ - // ハッシュ表にキーを入力し、値を取得 + /* 検索操作 */ + // ハッシュテーブルにキー key を入力し、値 value を取得 String name = map[15937]; /* 削除操作 */ - // ハッシュ表からキー値ペア (key, value) を削除 + // ハッシュテーブルからキーと値のペア (key, value) を削除 map.remove(10583); ``` @@ -241,51 +241,95 @@ ```rust title="hash_map.rs" use std::collections::HashMap; - /* ハッシュ表を初期化 */ + /* ハッシュテーブルを初期化 */ let mut map: HashMap = HashMap::new(); /* 追加操作 */ - // ハッシュ表にキー値ペア (key, value) を追加 - map.insert(12836, "小哈".to_string()); - map.insert(15937, "小啰".to_string()); - map.insert(16750, "小算".to_string()); - map.insert(13279, "小法".to_string()); - map.insert(10583, "小鸭".to_string()); + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map.insert(12836, "シャオハ".to_string()); + map.insert(15937, "シャオルオ".to_string()); + map.insert(16750, "シャオスワン".to_string()); + map.insert(13279, "シャオファ".to_string()); + map.insert(10583, "シャオヤー".to_string()); - /* クエリ操作 */ - // ハッシュ表にキーを入力し、値を取得 + /* 検索操作 */ + // ハッシュテーブルにキー key を入力し、値 value を取得 let _name: Option<&String> = map.get(&15937); /* 削除操作 */ - // ハッシュ表からキー値ペア (key, value) を削除 + // ハッシュテーブルからキーと値のペア (key, value) を削除 let _removed_value: Option = map.remove(&10583); ``` === "C" ```c title="hash_map.c" - // Cには組み込みのハッシュ表が提供されていません + // C には組み込みのハッシュテーブルはありません ``` === "Kotlin" ```kotlin title="hash_map.kt" + /* ハッシュテーブルを初期化 */ + val map = HashMap() + /* 追加操作 */ + // ハッシュテーブルにキーと値のペア (key, value) を追加 + map[12836] = "シャオハ" + map[15937] = "シャオルオ" + map[16750] = "シャオスワン" + map[13276] = "シャオファ" + map[10583] = "シャオヤー" + + /* 検索操作 */ + // ハッシュテーブルにキー key を入力し、値 value を取得 + val name = map[15937] + + /* 削除操作 */ + // ハッシュテーブルからキーと値のペア (key, value) を削除 + map.remove(10583) ``` -ハッシュ表を走査する一般的な方法は3つあります:キー値ペアの走査、キーの走査、値の走査。以下はコード例です: +=== "Ruby" + + ```ruby title="hash_map.rb" + # ハッシュテーブルを初期化 + hmap = {} + + # 追加操作 + # ハッシュテーブルにキーと値のペア (key, value) を追加 + hmap[12836] = "シャオハ" + hmap[15937] = "シャオルオ" + hmap[16750] = "シャオスワン" + hmap[13276] = "シャオファ" + hmap[10583] = "シャオヤー" + + # 検索操作 + # ハッシュテーブルにキー key を入力し、値 value を取得 + name = hmap[15937] + + # 削除操作 + # ハッシュテーブルからキーと値のペア (key, value) を削除 + hmap.delete(10583) + ``` + +??? pythontutor "可視化実行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%90%91%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E8%BE%93%E5%85%A5%E9%94%AE%20key%20%EF%BC%8C%E5%BE%97%E5%88%B0%E5%80%BC%20value%0A%20%20%20%20name%20%3D%20hmap%5B15937%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E5%88%A0%E9%99%A4%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap.pop%2810583%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +ハッシュテーブルには、キーと値のペア、キー、値を走査する 3 つの一般的な方法があります。コード例は以下のとおりです: === "Python" ```python title="hash_map.py" - # ハッシュ表を走査 - # キー値ペア key->value を走査 + # ハッシュテーブルを走査 + # キーと値のペア key->value を走査 for key, value in hmap.items(): print(key, "->", value) - # キーのみを走査 + # キー key のみを走査 for key in hmap.keys(): print(key) - # 値のみを走査 + # 値 value のみを走査 for value in hmap.values(): print(value) ``` @@ -293,12 +337,12 @@ === "C++" ```cpp title="hash_map.cpp" - /* ハッシュ表を走査 */ - // キー値ペア key->value を走査 + /* ハッシュテーブルを走査 */ + // キーと値のペア key->value を走査 for (auto kv: map) { cout << kv.first << " -> " << kv.second << endl; } - // イテレータを使用してキー値ペア key->value を走査 + // イテレータを使って key->value を走査 for (auto iter = map.begin(); iter != map.end(); iter++) { cout << iter->first << "->" << iter->second << endl; } @@ -307,16 +351,16 @@ === "Java" ```java title="hash_map.java" - /* ハッシュ表を走査 */ - // キー値ペア key->value を走査 - for (Map.Entry kv: map.entrySet()) { + /* ハッシュテーブルを走査 */ + // キーと値のペア key->value を走査 + for (Map.Entry kv: map.entrySet()) { System.out.println(kv.getKey() + " -> " + kv.getValue()); } - // キーのみを走査 + // キー key のみを走査 for (int key: map.keySet()) { System.out.println(key); } - // 値のみを走査 + // 値 value のみを走査 for (String val: map.values()) { System.out.println(val); } @@ -325,16 +369,16 @@ === "C#" ```csharp title="hash_map.cs" - /* ハッシュ表を走査 */ - // キー値ペア Key->Value を走査 + /* ハッシュテーブルを走査 */ + // キーと値のペア Key->Value を走査 foreach (var kv in map) { Console.WriteLine(kv.Key + " -> " + kv.Value); } - // キーのみを走査 + // キー key のみを走査 foreach (int key in map.Keys) { Console.WriteLine(key); } - // 値のみを走査 + // 値 value のみを走査 foreach (string val in map.Values) { Console.WriteLine(val); } @@ -343,16 +387,16 @@ === "Go" ```go title="hash_map_test.go" - /* ハッシュ表を走査 */ - // キー値ペア key->value を走査 + /* ハッシュテーブルを走査 */ + // キーと値のペア key->value を走査 for key, value := range hmap { fmt.Println(key, "->", value) } - // キーのみを走査 + // キー key のみを走査 for key := range hmap { fmt.Println(key) } - // 値のみを走査 + // 値 value のみを走査 for _, value := range hmap { fmt.Println(value) } @@ -361,16 +405,16 @@ === "Swift" ```swift title="hash_map.swift" - /* ハッシュ表を走査 */ - // キー値ペア Key->Value を走査 + /* ハッシュテーブルを走査 */ + // キーと値のペア Key->Value を走査 for (key, value) in map { print("\(key) -> \(value)") } - // キーのみを走査 + // キー Key のみを走査 for key in map.keys { print(key) } - // 値のみを走査 + // 値 Value のみを走査 for value in map.values { print(value) } @@ -379,16 +423,16 @@ === "JS" ```javascript title="hash_map.js" - /* ハッシュ表を走査 */ - console.info('\nキー値ペア Key->Value を走査'); + /* ハッシュテーブルを走査 */ + console.info('\nキーと値のペア Key->Value を走査'); for (const [k, v] of map.entries()) { console.info(k + ' -> ' + v); } - console.info('\nキーのみを走査 Key'); + console.info('\nキー Key のみを走査'); for (const k of map.keys()) { console.info(k); } - console.info('\n値のみを走査 Value'); + console.info('\n値 Value のみを走査'); for (const v of map.values()) { console.info(v); } @@ -397,16 +441,16 @@ === "TS" ```typescript title="hash_map.ts" - /* ハッシュ表を走査 */ - console.info('\nキー値ペア Key->Value を走査'); + /* ハッシュテーブルを走査 */ + console.info('\nキーと値のペア Key->Value を走査'); for (const [k, v] of map.entries()) { console.info(k + ' -> ' + v); } - console.info('\nキーのみを走査 Key'); + console.info('\nキー Key のみを走査'); for (const k of map.keys()) { console.info(k); } - console.info('\n値のみを走査 Value'); + console.info('\n値 Value のみを走査'); for (const v of map.values()) { console.info(v); } @@ -415,38 +459,38 @@ === "Dart" ```dart title="hash_map.dart" - /* ハッシュ表を走査 */ - // キー値ペア Key->Value を走査 + /* ハッシュテーブルを走査 */ + // キーと値のペア Key->Value を走査 map.forEach((key, value) { - print('$key -> $value'); + print('$key -> $value'); }); - // キーのみを走査 Key + // キー Key のみを走査 map.keys.forEach((key) { - print(key); + print(key); }); - // 値のみを走査 Value + // 値 Value のみを走査 map.values.forEach((value) { - print(value); + print(value); }); ``` === "Rust" ```rust title="hash_map.rs" - /* ハッシュ表を走査 */ - // キー値ペア Key->Value を走査 + /* ハッシュテーブルを走査 */ + // キーと値のペア Key->Value を走査 for (key, value) in &map { println!("{key} -> {value}"); } - // キーのみを走査 Key + // キー Key のみを走査 for key in map.keys() { println!("{key}"); } - // 値のみを走査 Value + // 値 Value のみを走査 for value in map.values() { println!("{value}"); } @@ -455,63 +499,93 @@ === "C" ```c title="hash_map.c" - // Cには組み込みのハッシュ表が提供されていません + // C には組み込みのハッシュテーブルはありません ``` === "Kotlin" ```kotlin title="hash_map.kt" - + /* ハッシュテーブルを走査 */ + // キーと値のペア key->value を走査 + for ((key, value) in map) { + println("$key -> $value") + } + // キー key のみを走査 + for (key in map.keys) { + println(key) + } + // 値 value のみを走査 + for (_val in map.values) { + println(_val) + } ``` -## ハッシュ表の簡単な実装 +=== "Ruby" -まず、最も簡単なケースを考えてみましょう:**配列のみを使ってハッシュ表を実装すること**。ハッシュ表において、配列の各空きスロットはバケットと呼ばれ、各バケットはキー値ペアを格納できます。したがって、クエリ操作は`key`に対応するバケットを見つけ、そこから`value`を取得することになります。 + ```ruby title="hash_map.rb" + # ハッシュテーブルを走査 + # キーと値のペア key->value を走査 + hmap.entries.each { |key, value| puts "#{key} -> #{value}" } -では、`key`に基づいて対応するバケットをどのように特定するのでしょうか?これはハッシュ関数によって実現されます。ハッシュ関数の役割は、より大きな入力空間をより小さな出力空間にマッピングすることです。ハッシュ表では、入力空間はすべてのキーで構成され、出力空間はすべてのバケット(配列インデックス)で構成されます。つまり、`key`が与えられた場合、**ハッシュ関数を使用して対応するキー値ペアの配列内の格納位置を決定できます**。 + # キー key のみを走査 + hmap.keys.each { |key| puts key } -与えられた`key`に対して、ハッシュ関数の計算は2つのステップで構成されます: + # 値 value のみを走査 + hmap.values.each { |val| puts val } + ``` -1. 特定のハッシュアルゴリズム`hash()`を使用してハッシュ値を計算します。 -2. ハッシュ値をバケット数(配列長)`capacity`で剰余を取り、キーに対応する配列`index`を取得します。 +??? pythontutor "可視化実行" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%E6%B7%BB%E5%8A%A0%E6%93%8D%E4%BD%9C%0A%20%20%20%20%23%20%E5%9C%A8%E5%93%88%E5%B8%8C%E8%A1%A8%E4%B8%AD%E6%B7%BB%E5%8A%A0%E9%94%AE%E5%80%BC%E5%AF%B9%20%28key,%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%E5%B0%8F%E5%93%88%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%E5%B0%8F%E5%95%B0%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%E5%B0%8F%E7%AE%97%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%E5%B0%8F%E6%B3%95%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%E5%B0%8F%E9%B8%AD%22%0A%20%20%20%20%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E5%93%88%E5%B8%8C%E8%A1%A8%0A%20%20%20%20%23%20%E9%81%8D%E5%8E%86%E9%94%AE%E5%80%BC%E5%AF%B9%20key-%3Evalue%0A%20%20%20%20for%20key,%20value%20in%20hmap.items%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key,%20%22-%3E%22,%20value%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E9%94%AE%20key%0A%20%20%20%20for%20key%20in%20hmap.keys%28%29%3A%0A%20%20%20%20%20%20%20%20print%28key%29%0A%20%20%20%20%23%20%E5%8D%95%E7%8B%AC%E9%81%8D%E5%8E%86%E5%80%BC%20value%0A%20%20%20%20for%20value%20in%20hmap.values%28%29%3A%0A%20%20%20%20%20%20%20%20print%28value%29&cumulative=false&curInstr=8&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + +## ハッシュテーブルの簡単な実装 + +まずは最も単純なケースとして、**1 つの配列だけでハッシュテーブルを実装する**ことを考えます。ハッシュテーブルでは、配列中の各空き位置をバケット(bucket)と呼び、各バケットには 1 つのキーと値のペアを格納できます。したがって、検索操作とは `key` に対応するバケットを見つけ、そのバケットから `value` を取得することです。 + +では、`key` に基づいて対応するバケットをどのように特定するのでしょうか。これはハッシュ関数(hash function)によって実現されます。ハッシュ関数の役割は、大きな入力空間をより小さな出力空間に写像することです。ハッシュテーブルでは、入力空間はすべての `key` 、出力空間はすべてのバケット(配列インデックス)です。言い換えると、`key` を入力すると、**ハッシュ関数によってその `key` に対応するキーと値のペアの配列内での格納位置を求められます**。 + +`key` を入力したとき、ハッシュ関数の計算過程は次の 2 段階に分かれます。 + +1. あるハッシュアルゴリズム `hash()` を用いてハッシュ値を計算します。 +2. ハッシュ値をバケット数(配列長)`capacity` で剰余し、その `key` に対応するバケット(配列インデックス)`index` を求めます。 ```shell index = hash(key) % capacity ``` -その後、`index`を使用してハッシュ表内の対応するバケットにアクセスし、`value`を取得できます。 +その後、`index` を使ってハッシュテーブル内の対応するバケットにアクセスし、`value` を取得できます。 -配列長が`capacity = 100`で、ハッシュアルゴリズムが`hash(key) = key`として定義されているとします。したがって、ハッシュ関数は`key % 100`として表現できます。以下の図は、`key`を学籍番号、`value`を名前として、ハッシュ関数の動作原理を示しています。 +配列長を `capacity = 100` 、ハッシュアルゴリズムを `hash(key) = key` とすると、ハッシュ関数は `key % 100` となります。次の図では、`key` を学籍番号、`value` を名前の例として、ハッシュ関数の動作原理を示します。 ![ハッシュ関数の動作原理](hash_map.assets/hash_function.png) -以下のコードは簡単なハッシュ表を実装しています。ここでは、`key`と`value`を`Pair`クラスにカプセル化してキー値ペアを表現しています。 +以下のコードは、単純なハッシュテーブルを実装したものです。ここでは、キーと値のペアを表すために `key` と `value` をクラス `Pair` にまとめています。 ```src [file]{array_hash_map}-[class]{array_hash_map}-[func]{} ``` -## ハッシュ衝突とリサイズ +## ハッシュ衝突と拡張 -本質的に、ハッシュ関数の役割は、すべてのキーの入力空間全体を、すべての配列インデックスの出力空間にマッピングすることです。しかし、入力空間は出力空間よりもはるかに大きいことがよくあります。したがって、**理論的には、「複数の入力が同じ出力に対応する」ケースが常に存在します**。 +本質的には、ハッシュ関数の役割は、すべての `key` からなる入力空間を、配列のすべてのインデックスからなる出力空間に写像することです。しかし、入力空間は多くの場合、出力空間よりはるかに大きいため、**理論上は必ず「複数の入力が同じ出力に対応する」状況が存在します**。 -上記の例では、与えられたハッシュ関数で、入力`key`の下二桁が同じ場合、ハッシュ関数は同じ出力を生成します。例えば、学籍番号12836と20336の2人の学生をクエリすると、以下のことがわかります: +上の例のハッシュ関数では、入力 `key` の下 2 桁が同じであれば、出力結果も同じになります。たとえば、学籍番号 12836 と 20336 の 2 人の学生を検索すると、次の結果を得ます: ```shell 12836 % 100 = 36 20336 % 100 = 36 ``` -下図に示すように、両方の学籍番号が同じ名前を指しており、これは明らかに間違っています。この複数の入力が同じ出力に対応する状況をハッシュ衝突と呼びます。 +次の図に示すように、2 つの学籍番号が同じ名前を指してしまっており、これは明らかに誤りです。このような、複数の入力が同じ出力に対応する状況をハッシュ衝突(hash collision)と呼びます。 ![ハッシュ衝突の例](hash_map.assets/hash_collision.png) -ハッシュ表の容量$n$が増加するにつれて、複数のキーが同じバケットに割り当てられる確率が減少し、衝突が少なくなることは理解しやすいです。したがって、**ハッシュ表をリサイズすることでハッシュ衝突を減らすことができます**。 +容易に分かるように、ハッシュテーブルの容量 $n$ が大きいほど、複数の `key` が同じバケットに割り当てられる確率は低くなり、衝突も少なくなります。したがって、**ハッシュテーブルを拡張することでハッシュ衝突を減らせます**。 -下図に示すように、リサイズ前は、キー値ペア`(136, A)`と`(236, D)`が衝突していました。しかし、リサイズ後は衝突が解決されています。 +次の図に示すように、拡張前はキーと値のペア `(136, A)` と `(236, D)` が衝突していますが、拡張後は衝突が解消されます。 -![ハッシュ表のリサイズ](hash_map.assets/hash_table_reshash.png) +![ハッシュテーブルの拡張](hash_map.assets/hash_table_reshash.png) -配列の拡張と同様に、ハッシュ表のリサイズにはすべてのキー値ペアを元のハッシュ表から新しいものに移行する必要があり、時間がかかります。さらに、ハッシュ表の`capacity`が変更されるため、ハッシュ関数を使用してすべてのキー値ペアの格納位置を再計算する必要があり、リサイズプロセスの計算オーバーヘッドがさらに増加します。したがって、プログラミング言語は頻繁なリサイズを防ぐために、ハッシュ表に十分大きな容量を割り当てることがよくあります。 +配列の拡張と同様に、ハッシュテーブルの拡張ではすべてのキーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移し替える必要があり、非常に時間がかかります。また、ハッシュテーブルの容量 `capacity` が変わるため、ハッシュ関数を使ってすべてのキーと値のペアの格納位置を再計算しなければならず、これによって拡張過程の計算コストがさらに増加します。そのため、プログラミング言語では通常、頻繁な拡張を防ぐために十分大きなハッシュテーブル容量をあらかじめ確保します。 -負荷率はハッシュ表の重要な概念です。ハッシュ表内の要素数とバケット数の比率として定義されます。ハッシュ衝突の深刻度を測定するために使用され、**しばしばハッシュ表のリサイズのトリガーとしても機能します**。例えば、Javaでは、負荷率が$0.75$を超えると、システムはハッシュ表を元のサイズの2倍にリサイズします。 +負荷率(load factor)はハッシュテーブルにおける重要な概念であり、ハッシュテーブル内の要素数をバケット数で割ったものとして定義され、ハッシュ衝突の深刻さを測るために用いられます。**また、ハッシュテーブル拡張の発動条件としてもよく使われます**。例えば Java では、負荷率が $0.75$ を超えると、システムはハッシュテーブルを元の $2$ 倍に拡張します。 diff --git a/ja/docs/chapter_hashing/index.md b/ja/docs/chapter_hashing/index.md index a4f859736..69b49eeaf 100644 --- a/ja/docs/chapter_hashing/index.md +++ b/ja/docs/chapter_hashing/index.md @@ -1,9 +1,9 @@ -# ハッシュ表 +# ハッシュテーブル -![ハッシュ表](../assets/covers/chapter_hashing.jpg) +![ハッシュテーブル](../assets/covers/chapter_hashing.jpg) !!! abstract - コンピューティングの世界において、ハッシュ表は賢い司書のようなものです。 - - インデックス番号の計算方法を理解し、目的の本を迅速に取得することを可能にします。 + コンピュータの世界では、ハッシュテーブルは聡明な図書館員のような存在です。 + + 彼は請求記号の計算方法を知っており、そのため目的の本を素早く見つけられます。 diff --git a/ja/docs/chapter_hashing/summary.md b/ja/docs/chapter_hashing/summary.md index 413b6ef83..b3f94df3b 100644 --- a/ja/docs/chapter_hashing/summary.md +++ b/ja/docs/chapter_hashing/summary.md @@ -1,47 +1,51 @@ # まとめ -### 重要なポイント +### 重要ポイントの振り返り -- 入力`key`が与えられると、ハッシュ表は$O(1)$の時間で対応する`value`を取得でき、非常に効率的です。 -- 一般的なハッシュ表の操作には、クエリ、キー値ペアの追加、キー値ペアの削除、ハッシュ表の走査があります。 -- ハッシュ関数は`key`を配列インデックスにマッピングし、対応するバケットにアクセスして`value`を取得できるようにします。 -- 2つの異なるキーがハッシュ化後に同じ配列インデックスになる場合があり、誤ったクエリ結果につながります。この現象はハッシュ衝突として知られています。 -- ハッシュ表の容量が大きいほど、ハッシュ衝突の確率は低くなります。したがって、ハッシュ表のリサイズはハッシュ衝突を緩和できます。配列のリサイズと同様に、ハッシュ表のリサイズはコストが高いです。 -- 要素数をバケット数で割った負荷率は、ハッシュ衝突の深刻度を反映し、しばしばハッシュ表リサイズのトリガー条件として使用されます。 -- 連鎖法は各要素を連結リストに変換し、衝突するすべての要素を同じリストに格納することでハッシュ衝突に対処します。ただし、過度に長いリストはクエリ効率を低下させる可能性があり、リストを赤黒木に変換することで改善できます。 -- オープンアドレス法は複数回のプローブを通してハッシュ衝突を処理します。線形プローブは固定ステップサイズを使用しますが、要素を削除できず、クラスタリングを起こしやすい傾向があります。多重ハッシュはプローブに複数のハッシュ関数を使用し、線形プローブと比較してクラスタリングを減らしますが、計算オーバーヘッドが増加します。 -- 異なるプログラミング言語はさまざまなハッシュ表実装を採用しています。例えば、Javaの`HashMap`は連鎖法を使用し、Pythonの`dict`はオープンアドレス法を採用しています。 -- ハッシュ表では、決定性、高効率、均等分散を持つハッシュアルゴリズムが望まれます。暗号化では、ハッシュアルゴリズムは衝突耐性と雪崩効果も持つべきです。 -- ハッシュアルゴリズムは通常、ハッシュ値の均等分散を保証し、ハッシュ衝突を減らすために、大きな素数を剰余として使用します。 -- 一般的なハッシュアルゴリズムには、MD5、SHA-1、SHA-2、SHA-3があります。MD5はファイル整合性チェックによく使用され、SHA-2は安全なアプリケーションとプロトコルで一般的に使用されます。 -- プログラミング言語は通常、ハッシュ表のバケットインデックスを計算するために、データ型に対して組み込みのハッシュアルゴリズムを提供します。一般的に、不変オブジェクトのみがハッシュ可能です。 +- `key` を入力すると、ハッシュテーブルは $O(1)$ 時間で `value` を検索でき、非常に高効率である。 +- 一般的なハッシュテーブルの操作には、検索、キーと値のペアの追加、キーと値のペアの削除、ハッシュテーブルの走査などがある。 +- ハッシュ関数は `key` を配列インデックスに写像し、それによって対応するバケットにアクセスして `value` を取得する。 +- 異なる 2 つの `key` が、ハッシュ関数を通した後に同じ配列インデックスになることがあり、検索結果の誤りを引き起こす。この現象をハッシュ衝突と呼ぶ。 +- ハッシュテーブルの容量が大きいほど、ハッシュ衝突の確率は低くなる。そのため、ハッシュテーブルを拡張することでハッシュ衝突を緩和できる。配列の拡張と同様に、ハッシュテーブルの拡張操作のコストは大きい。 +- 負荷率は、ハッシュテーブル内の要素数をバケット数で割ったものと定義され、ハッシュ衝突の深刻さを反映する。ハッシュテーブル拡張を発動する条件としてよく用いられる。 +- 連鎖方式では、単一要素を連結リストに変換し、衝突したすべての要素を同じ連結リストに格納する。しかし、連結リストが長すぎると検索効率が低下するため、さらに連結リストを赤黒木に変換して効率を高めることができる。 +- オープンアドレス法は複数回の探索によってハッシュ衝突を処理する。線形探索は固定のステップ幅を用いるが、要素を削除できず、クラスタリングが発生しやすいという欠点がある。二重ハッシュは複数のハッシュ関数を用いて探索するため、線形探索に比べてクラスタリングが起きにくいが、複数のハッシュ関数によって計算量が増える。 +- プログラミング言語ごとに、異なるハッシュテーブル実装が採用されている。たとえば、Java の `HashMap` は連鎖方式を使用し、Python の `Dict` はオープンアドレス法を採用している。 +- ハッシュテーブルでは、ハッシュアルゴリズムに決定性、高効率、均一分布という特徴が求められる。暗号学では、ハッシュアルゴリズムはさらに耐衝突性とアバランシェ効果も備えるべきである。 +- ハッシュアルゴリズムは通常、大きな素数を法として用い、ハッシュ値の均一分布を最大限に保証してハッシュ衝突を減らす。 +- 一般的なハッシュアルゴリズムには MD5、SHA-1、SHA-2、SHA-3 などがある。MD5 はファイル完全性の検証によく用いられ、SHA-2 はセキュリティ用途やプロトコルでよく用いられる。 +- プログラミング言語は通常、データ型に対して組み込みのハッシュアルゴリズムを提供し、ハッシュテーブル内のバケットインデックスの計算に用いる。通常、ハッシュ可能なのは不変オブジェクトだけである。 ### Q & A -**Q**: ハッシュ表の時間計算量が$O(n)$に悪化するのはいつですか? +**Q**:ハッシュテーブルの時間計算量が $O(n)$ になるのはどのような場合ですか? -ハッシュ表の時間計算量は、ハッシュ衝突が深刻な場合に$O(n)$に悪化する可能性があります。ハッシュ関数が適切に設計され、容量が適切に設定され、衝突が均等に分散されている場合、時間計算量は$O(1)$です。プログラミング言語の組み込みハッシュ表を使用する場合、通常は時間計算量を$O(1)$と考えます。 +ハッシュ衝突が深刻な場合、ハッシュテーブルの時間計算量は $O(n)$ に劣化する。ハッシュ関数の設計が適切で、容量設定が合理的で、衝突が比較的均等な場合、時間計算量は $O(1)$ である。プログラミング言語組み込みのハッシュテーブルを使うとき、通常は時間計算量を $O(1)$ とみなす。 -**Q**: なぜハッシュ関数$f(x) = x$を使用しないのですか?これなら衝突を排除できます。 +**Q**:なぜハッシュ関数 $f(x) = x$ を使わないのですか? そうすれば衝突は起きません。 -ハッシュ関数$f(x) = x$では、各要素が一意のバケットインデックスに対応し、これは配列と同等です。しかし、入力空間は通常出力空間(配列長)よりもはるかに大きいため、ハッシュ関数の最後のステップは配列長の剰余を取ることがよくあります。言い換えると、ハッシュ表の目標は、$O(1)$のクエリ効率を提供しながら、より大きな状態空間をより小さなものにマッピングすることです。 +$f(x) = x$ というハッシュ関数では、各要素は一意のバケットインデックスに対応し、これは配列と等価である。しかし、入力空間は通常、出力空間(配列長)よりはるかに大きいため、ハッシュ関数の最後のステップはたいてい配列長での剰余になる。言い換えると、ハッシュテーブルの目的は、大きな状態空間をより小さな空間に写像し、$O(1)$ の検索効率を提供することである。 -**Q**: ハッシュ表がこれらの構造を使って実装されているにもかかわらず、なぜ配列、連結リスト、二分木よりも効率的になれるのですか? +**Q**:ハッシュテーブルの基礎実装は配列、連結リスト、二分木なのに、なぜそれらより高効率になり得るのですか? -まず、ハッシュ表は時間効率が高いですが、空間効率は低いです。ハッシュ表のメモリの大部分は未使用のままです。 +まず、ハッシュテーブルは時間効率が高くなる一方で、空間効率は低くなる。ハッシュテーブルには、かなりの部分で未使用のメモリが存在する。 -次に、ハッシュ表は特定のユースケースでのみ時間効率が高いです。配列や連結リストを使用して同じ時間計算量で機能を実装できる場合、通常はハッシュ表を使用するよりも高速です。これは、ハッシュ関数の計算がオーバーヘッドを発生させ、時間計算量の定数因子が大きくなるためです。 +次に、時間効率が高くなるのは特定の利用場面に限られる。ある機能が同じ時間計算量で配列や連結リストによって実装できるなら、通常はハッシュテーブルより速い。これは、ハッシュ関数の計算にコストがかかり、時間計算量の定数項がより大きいからである。 -最後に、ハッシュ表の時間計算量は悪化する可能性があります。例えば、連鎖法では、連結リストや赤黒木で検索操作を実行し、これは依然として$O(n)$時間に悪化するリスクがあります。 +最後に、ハッシュテーブルの時間計算量は劣化する可能性がある。たとえば連鎖方式では、連結リストや赤黒木で検索操作を行うため、なお $O(n)$ 時間に劣化するリスクがある。 -**Q**: 多重ハッシュにも要素を直接削除できないという欠陥がありますか?削除としてマークされた空間は再利用できますか? +**Q**:二重ハッシュにも要素を直接削除できない欠点がありますか? 削除済みとマークした領域は再利用できますか? -多重ハッシュはオープンアドレス法の一形態であり、すべてのオープンアドレス法には要素を直接削除できないという欠点があります。要素を削除済みとしてマークする必要があります。マークされた空間は再利用できます。ハッシュ表に新しい要素を挿入する際、ハッシュ関数が削除済みとしてマークされた位置を指している場合、その位置は新しい要素によって使用できます。これにより、ハッシュ表のプローブシーケンスを維持しながら、空間の効率的な使用が保証されます。 +二重ハッシュはオープンアドレス法の一種であり、オープンアドレス法はいずれも要素を直接削除できないという欠点があるため、削除のマーク付けが必要になる。削除済みとマークされた領域は再利用できる。新しい要素をハッシュテーブルに挿入し、ハッシュ関数によって削除済みとマークされた位置を見つけた場合、その位置は新しい要素に使用できる。こうすることで、ハッシュテーブルの探索系列を変えずに保ちつつ、空間利用率も確保できる。 -**Q**: なぜ線形プローブの検索プロセス中にハッシュ衝突が発生するのですか? +**Q**:なぜ線形探索では、要素を探すときにハッシュ衝突が発生するのですか? -検索プロセス中、ハッシュ関数は対応するバケットとキー値ペアを指します。`key`が一致しない場合、ハッシュ衝突を示します。したがって、線形プローブは正しいキー値ペアが見つかるか検索が失敗するまで、事前に決められたステップサイズで下方向に検索します。 +探索時には、ハッシュ関数で対応するバケットとキーと値のペアを見つけ、`key` が一致しないことが分かると、それはハッシュ衝突を意味する。そのため、線形探索法では事前に設定したステップ幅に従って順に探索し、正しいキーと値のペアを見つけるか、見つからずに終了するまで続ける。 -**Q**: なぜハッシュ表のリサイズがハッシュ衝突を緩和できるのですか? +**Q**:なぜハッシュテーブルの拡張でハッシュ衝突を緩和できるのですか? -ハッシュ関数の最後のステップは、出力を配列インデックス範囲内に保つために、配列長$n$の剰余を取ることがよくあります。リサイズ時、配列長$n$が変化し、キーに対応するインデックスも変化する可能性があります。以前に同じバケットにマッピングされていたキーが、リサイズ後に複数のバケットに分散される可能性があり、それによってハッシュ衝突が緩和されます。 +ハッシュ関数の最後のステップは、たいてい配列長 $n$ での剰余を取り、出力値を配列インデックスの範囲内に収めることである。拡張後は配列長 $n$ が変化し、`key` に対応するインデックスも変化する可能性がある。もともと同じバケットに入っていた複数の `key` は、拡張後には複数のバケットに割り当てられる可能性があり、それによってハッシュ衝突が緩和される。 + +**Q**:高効率な読み書きのためなら、配列を直接使えばよいのではないですか? + +データの `key` が連続した小範囲の整数であれば、配列を直接使えばよく、単純で高効率である。しかし `key` が別の型(たとえば文字列)の場合は、ハッシュ関数を用いて `key` を配列インデックスに写像し、さらにバケット配列を通じて要素を格納する必要がある。このような構造がハッシュテーブルである。 diff --git a/ja/docs/chapter_heap/build_heap.md b/ja/docs/chapter_heap/build_heap.md index 311a53645..404a313c0 100644 --- a/ja/docs/chapter_heap/build_heap.md +++ b/ja/docs/chapter_heap/build_heap.md @@ -1,67 +1,67 @@ -# ヒープ構築操作 +# ヒープ構築 -場合によっては、リストのすべての要素を使用してヒープを構築したいことがあり、このプロセスは「ヒープ構築操作」として知られています。 +場合によっては、リスト内のすべての要素を使ってヒープを構築したいことがあります。この過程を「ヒープ構築」と呼びます。 -## ヒープ挿入操作による実装 +## ヒープへの挿入操作による実現 -まず、空のヒープを作成し、次にリストを反復処理して、各要素に対して順番に「ヒープ挿入操作」を実行します。これは、要素をヒープの末尾に追加し、次に下から上に「ヒープ化」することを意味します。 +まず空のヒープを作成し、次にリストを走査して、各要素に対して順に「ヒープへの挿入操作」を実行します。つまり、要素をヒープの末尾に追加してから、その要素に対して「下から上へ」のヒープ化を行います。 -ヒープに要素が追加されるたびに、ヒープの長さは1つずつ増加します。ノードは二分木に上から下に追加されるため、ヒープは「上から下に」構築されます。 +要素が1つヒープに挿入されるたびに、ヒープの長さは1増加します。ノードは上から下へ順に二分木へ追加されるため、ヒープは「上から下へ」構築されます。 -要素数を$n$とすると、各要素の挿入操作は$O(\log{n})$時間かかるため、このヒープ構築方法の時間計算量は$O(n \log n)$です。 +要素数を $n$ とすると、各要素のヒープへの挿入操作には $O(\log{n})$ の時間がかかるため、このヒープ構築法の時間計算量は $O(n \log n)$ です。 -## 走査によるヒープ化の実装 +## 走査によるヒープ化で実現 -実際には、2つのステップでより効率的なヒープ構築方法を実装できます。 +実際には、より効率的なヒープ構築法を実現でき、全体は2つの手順に分かれます。 1. リストのすべての要素をそのままヒープに追加します。この時点では、ヒープの性質はまだ満たされていません。 -2. ヒープを逆順(レベル順走査の逆)で走査し、各非葉ノードに対して「上から下のヒープ化」を実行します。 +2. ヒープを逆順で走査し(レベル順走査の逆順)、各非葉ノードに対して順に「上から下へ」のヒープ化を実行します。 -**ノードをヒープ化した後、そのノードを根とする部分木は有効な部分ヒープになります**。走査が逆順であるため、ヒープは「下から上に」構築されます。 +**あるノードをヒープ化するたびに、そのノードを根とする部分木は合法な部分ヒープになります**。また、逆順で走査するため、ヒープは「下から上へ」構築されます。 -逆走査を選択する理由は、現在のノードの下の部分木がすでに有効な部分ヒープであることを保証し、現在のノードのヒープ化を効果的にするためです。 +逆順走査を選ぶのは、この方法なら現在のノードの下にある部分木がすでに合法な部分ヒープであることを保証でき、そのうえで現在のノードをヒープ化してはじめて有効になるからです。 -言及する価値があるのは、**葉ノードは子を持たないため、自然に有効な部分ヒープを形成し、ヒープ化する必要がない**ということです。以下のコードに示すように、最後の非葉ノードは最後のノードの親です。そこから開始して逆順に走査してヒープ化を実行します: +なお、**葉ノードには子ノードがないため、それ自体が自然に合法な部分ヒープであり、ヒープ化は不要です**。以下のコードが示すように、最後の非葉ノードは最後のノードの親ノードであり、そこから逆順に走査してヒープ化を実行します。 ```src [file]{my_heap}-[class]{max_heap}-[func]{__init__} ``` -## 計算量分析 +## 計算量の分析 -次に、この第2のヒープ構築方法の時間計算量を計算してみましょう。 +以下では、2つ目のヒープ構築法の時間計算量を求めてみましょう。 -- 完備二分木のノード数を$n$と仮定すると、葉ノードの数は$(n + 1) / 2$です。ここで$/$ は整数除算です。したがって、ヒープ化が必要なノードの数は$(n - 1) / 2$です。 -- 「上から下のヒープ化」のプロセスでは、各ノードは最大で葉ノードまでヒープ化されるため、最大反復回数は二分木の高さ$\log n$です。 +- 完全二分木のノード数を $n$ とすると、葉ノード数は $(n + 1) / 2$ です。ここで $/$ は切り捨て除算を表します。したがって、ヒープ化が必要なノード数は $(n - 1) / 2$ です。 +- 上から下へのヒープ化の過程では、各ノードは最大で葉ノードまでヒープ化されるため、最大反復回数は二分木の高さ $\log n$ です。 -この2つを掛け合わせると、ヒープ構築プロセスの時間計算量は$O(n \log n)$となります。**しかし、この推定は正確ではありません。二分木の下位レベルには上位よりもはるかに多くのノードがあるという性質を考慮していないからです。** +上の2つを掛け合わせると、ヒープ構築過程の時間計算量は $O(n \log n)$ となります。**しかし、この見積もりは正確ではありません。二分木では下層のノード数が上層よりはるかに多いという性質を考慮していないためです**。 -より正確な計算を行いましょう。計算を簡素化するため、$n$個のノードと高さ$h$を持つ「完全二分木」を仮定します。この仮定は結果の正確性に影響しません。 +次に、より正確な計算を行います。計算を簡単にするため、ノード数が $n$ 、高さが $h$ の「満二分木」を仮定します。この仮定は計算結果の正しさに影響しません。 -![完全二分木の各レベルのノード数](build_heap.assets/heapify_operations_count.png) +![満二分木の各層のノード数](build_heap.assets/heapify_operations_count.png) -上図に示すように、ノードが「上から下にヒープ化される」最大反復回数は、そのノードから葉ノードまでの距離と等しく、これは正確に「ノードの高さ」です。したがって、各レベルで「ノード数×ノードの高さ」を合計して、**すべてのノードの総ヒープ化反復回数を得る**ことができます。 +上図に示すように、ノードを「上から下へヒープ化」する最大反復回数は、そのノードから葉ノードまでの距離に等しく、この距離こそが「ノードの高さ」です。したがって、各層の「ノード数 $\times$ ノードの高さ」を合計すれば、**すべてのノードのヒープ化反復回数の総和**が得られます。 $$ T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{(h-1)}\times1 $$ -上記の方程式を簡素化するために、高校の数列の知識を使用する必要があります。まず$T(h)$に$2$を掛けて以下を得ます: +上式を簡約するには中学の数列の知識を用います。まず $T(h)$ に $2$ を掛けると、次のようになります。 $$ \begin{aligned} T(h) & = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{h-1}\times1 \newline -2T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \dots + 2^h\times1 \newline +2 T(h) & = 2^1h + 2^2(h-1) + 2^3(h-2) + \dots + 2^{h}\times1 \newline \end{aligned} $$ -変位法を使用して$2T(h)$から$T(h)$を減算すると、以下を得ます: +ずらして引く方法を用い、下式の $2 T(h)$ から上式の $T(h)$ を引くと、次が得られます。 $$ 2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \dots + 2^{h-1} + 2^h $$ -方程式を観察すると、$T(h)$は等比数列であり、和の公式を使用して直接計算でき、時間計算量は以下になります: +上式を見ると、$T(h)$ は等比数列であることがわかるため、和の公式を直接用いて、時間計算量は次のように求められます。 $$ \begin{aligned} @@ -71,4 +71,4 @@ T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline \end{aligned} $$ -さらに、高さ$h$の完全二分木は$n = 2^{h+1} - 1$個のノードを持つため、計算量は$O(2^h) = O(n)$です。この計算は、**リストを入力してヒープを構築する時間計算量が$O(n)$であり、非常に効率的である**ことを示しています。 +さらに、高さ $h$ の満二分木のノード数は $n = 2^{h+1} - 1$ であるため、計算量は容易に $O(2^h) = O(n)$ とわかります。以上の導出は、**入力リストからヒープを構築する時間計算量が $O(n)$ であり、非常に効率的である**ことを示しています。 diff --git a/ja/docs/chapter_heap/heap.md b/ja/docs/chapter_heap/heap.md index b355c41e2..17d65f569 100644 --- a/ja/docs/chapter_heap/heap.md +++ b/ja/docs/chapter_heap/heap.md @@ -1,64 +1,64 @@ # ヒープ -ヒープは特定の条件を満たす完備二分木で、主に次の2つのタイプに分類されます(下図参照)。 +ヒープ(heap)は、特定の条件を満たす完全二分木であり、主に次の 2 種類に分けられます。 -- 最小ヒープ:任意のノードの値 $\leq$ その子ノードの値。 -- 最大ヒープ:任意のノードの値 $\geq$ その子ノードの値。 +- 最小ヒープ(min heap):任意のノードの値 $\leq$ その子ノードの値。 +- 最大ヒープ(max heap):任意のノードの値 $\geq$ その子ノードの値。 ![最小ヒープと最大ヒープ](heap.assets/min_heap_and_max_heap.png) -完備二分木の特別なケースとして、ヒープには以下の特性があります: +ヒープは完全二分木の特殊な例であり、次の性質を持ちます。 -- 最下位層のノードは左から右に埋められ、他の層のノードは完全に埋められています。 -- 二分木の根ノードをヒープの「先頭」と呼び、最も右下のノードをヒープの「末尾」と呼びます。 -- 最大ヒープ(最小ヒープ)の場合、先頭要素(根)の値はすべての要素の中で最大(最小)です。 +- 最下層のノードは左から順に埋められ、ほかの層のノードはすべて埋まっています。 +- 二分木の根ノードを「ヒープ頂点」、最下層で最も右にあるノードを「ヒープ底」と呼びます。 +- 最大ヒープ(最小ヒープ)では、ヒープ頂点の要素(根ノード)の値が最大(最小)です。 -## ヒープの一般的な操作 +## ヒープの基本操作 -多くのプログラミング言語が優先度キューを提供していることに注意してください。これは優先度付きソートを持つキューとして定義される抽象データ構造です。 +ここで注意したいのは、多くのプログラミング言語が提供しているのは優先度付きキュー(priority queue)であり、これは優先度順に並ぶキューとして定義される抽象データ構造だということです。 -実際には、**ヒープは優先度キューを実装するためによく使用されます。最大ヒープは、要素が降順でデキューされる優先度キューに対応します**。使用の観点から、「優先度キュー」と「ヒープ」を同等のデータ構造と考えることができます。したがって、この本では両者を特別に区別せず、統一して「ヒープ」と呼びます。 +実際には、**ヒープは通常、優先度付きキューの実装に用いられ、最大ヒープは要素が大きい順に取り出される優先度付きキューに相当します**。利用の観点では、「優先度付きキュー」と「ヒープ」は等価なデータ構造とみなせます。そのため、本書では両者を特に区別せず、まとめて「ヒープ」と呼びます。 -ヒープの一般的な操作を下表に示します。メソッド名はプログラミング言語によって異なる場合があります。 +ヒープの基本操作を以下の表に示します。メソッド名はプログラミング言語によって異なります。 -

  ヒープ操作の効率

+

  ヒープの操作効率

-| メソッド名 | 説明 | 時間計算量 | -| ----------- | ------------------------------------------------- | ----------- | -| `push()` | ヒープに要素を追加 | $O(\log n)$ | -| `pop()` | ヒープから先頭要素を削除 | $O(\log n)$ | -| `peek()` | 先頭要素にアクセス(最大/最小ヒープの場合、最大/最小値) | $O(1)$ | -| `size()` | ヒープ内の要素数を取得 | $O(1)$ | -| `isEmpty()` | ヒープが空かどうかをチェック | $O(1)$ | +| メソッド名 | 説明 | 時間計算量 | +| ----------- | ------------------------------------------------ | ----------- | +| `push()` | 要素をヒープに追加 | $O(\log n)$ | +| `pop()` | ヒープ頂点の要素を取り出す | $O(\log n)$ | +| `peek()` | ヒープ頂点の要素にアクセス(最大 / 最小ヒープではそれぞれ最大 / 最小値) | $O(1)$ | +| `size()` | ヒープ内の要素数を取得 | $O(1)$ | +| `isEmpty()` | ヒープが空かどうかを判定 | $O(1)$ | -実際には、プログラミング言語によって提供されるヒープクラス(または優先度キュークラス)を直接使用できます。 +実際の応用では、プログラミング言語が提供するヒープクラス(または優先度付きキュークラス)をそのまま使えます。 -ソートアルゴリズムで「昇順」と「降順」があるように、`flag`を設定するか`Comparator`を変更することで「最小ヒープ」と「最大ヒープ」を切り替えることができます。コードは以下の通りです: +ソートアルゴリズムにおける「昇順」と「降順」と同様に、`flag` を設定したり `Comparator` を変更したりすることで、「最小ヒープ」と「最大ヒープ」を切り替えられます。コードは以下のとおりです: === "Python" ```python title="heap.py" - # 最小ヒープの初期化 + # 最小ヒープを初期化 min_heap, flag = [], 1 - # 最大ヒープの初期化 + # 最大ヒープを初期化 max_heap, flag = [], -1 - # Pythonのheapqモジュールはデフォルトで最小ヒープを実装 - # 要素をヒープにプッシュする前に負の値にすることで、順序を反転させ、最大ヒープを実装 - # この例では、flag = 1は最小ヒープに対応し、flag = -1は最大ヒープに対応 + # Python の heapq モジュールはデフォルトで最小ヒープを実装している + # 「要素を負にして」からヒープに追加すると、大小関係を反転させて最大ヒープを実現できる + # この例では、flag = 1 のときは最小ヒープ、flag = -1 のときは最大ヒープに対応する - # ヒープに要素をプッシュ + # 要素をヒープに追加 heapq.heappush(max_heap, flag * 1) heapq.heappush(max_heap, flag * 3) heapq.heappush(max_heap, flag * 2) heapq.heappush(max_heap, flag * 5) heapq.heappush(max_heap, flag * 4) - # ヒープの先頭要素を取得 + # ヒープ頂点の要素を取得 peek: int = flag * max_heap[0] # 5 - # ヒープの先頭要素をポップ - # ポップされた要素は降順のシーケンスを形成 + # ヒープ頂点の要素を取り出す + # 取り出された要素は大きい順の列になる val = flag * heapq.heappop(max_heap) # 5 val = flag * heapq.heappop(max_heap) # 4 val = flag * heapq.heappop(max_heap) # 3 @@ -68,10 +68,10 @@ # ヒープのサイズを取得 size: int = len(max_heap) - # ヒープが空かどうかをチェック + # ヒープが空かどうかを判定 is_empty: bool = not max_heap - # リストからヒープを作成 + # 入力リストからヒープを構築 min_heap: list[int] = [1, 3, 2, 5, 4] heapq.heapify(min_heap) ``` @@ -79,24 +79,24 @@ === "C++" ```cpp title="heap.cpp" - /* ヒープの初期化 */ - // 最小ヒープの初期化 + /* ヒープを初期化 */ + // 最小ヒープを初期化 priority_queue, greater> minHeap; - // 最大ヒープの初期化 + // 最大ヒープを初期化 priority_queue, less> maxHeap; - /* ヒープに要素をプッシュ */ + /* 要素をヒープに追加 */ maxHeap.push(1); maxHeap.push(3); maxHeap.push(2); maxHeap.push(5); maxHeap.push(4); - /* ヒープの先頭要素を取得 */ + /* ヒープ頂点の要素を取得 */ int peek = maxHeap.top(); // 5 - /* ヒープの先頭要素をポップ */ - // ポップされた要素は降順のシーケンスを形成 + /* ヒープ頂点の要素を取り出す */ + // 取り出された要素は大きい順の列になる maxHeap.pop(); // 5 maxHeap.pop(); // 4 maxHeap.pop(); // 3 @@ -106,10 +106,10 @@ /* ヒープのサイズを取得 */ int size = maxHeap.size(); - /* ヒープが空かどうかをチェック */ + /* ヒープが空かどうかを判定 */ bool isEmpty = maxHeap.empty(); - /* リストからヒープを作成 */ + /* 入力リストからヒープを構築 */ vector input{1, 3, 2, 5, 4}; priority_queue, greater> minHeap(input.begin(), input.end()); ``` @@ -117,24 +117,24 @@ === "Java" ```java title="heap.java" - /* ヒープの初期化 */ - // 最小ヒープの初期化 + /* ヒープを初期化 */ + // 最小ヒープを初期化 Queue minHeap = new PriorityQueue<>(); - // 最大ヒープの初期化(ラムダ式でComparatorを変更するだけ) + // 最大ヒープを初期化(lambda 式で Comparator を変更すればよい) Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); - /* ヒープに要素をプッシュ */ + /* 要素をヒープに追加 */ maxHeap.offer(1); maxHeap.offer(3); maxHeap.offer(2); maxHeap.offer(5); maxHeap.offer(4); - /* ヒープの先頭要素を取得 */ + /* ヒープ頂点の要素を取得 */ int peek = maxHeap.peek(); // 5 - /* ヒープの先頭要素をポップ */ - // ポップされた要素は降順のシーケンスを形成 + /* ヒープ頂点の要素を取り出す */ + // 取り出された要素は大きい順の列になる peek = maxHeap.poll(); // 5 peek = maxHeap.poll(); // 4 peek = maxHeap.poll(); // 3 @@ -144,34 +144,34 @@ /* ヒープのサイズを取得 */ int size = maxHeap.size(); - /* ヒープが空かどうかをチェック */ + /* ヒープが空かどうかを判定 */ boolean isEmpty = maxHeap.isEmpty(); - /* リストからヒープを作成 */ + /* 入力リストからヒープを構築 */ minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); ``` === "C#" ```csharp title="heap.cs" - /* ヒープの初期化 */ - // 最小ヒープの初期化 + /* ヒープを初期化 */ + // 最小ヒープを初期化 PriorityQueue minHeap = new(); - // 最大ヒープの初期化(ラムダ式でComparatorを変更するだけ) - PriorityQueue maxHeap = new(Comparer.Create((x, y) => y - x)); + // 最大ヒープを初期化(lambda 式で Comparer を変更すればよい) + PriorityQueue maxHeap = new(Comparer.Create((x, y) => y.CompareTo(x))); - /* ヒープに要素をプッシュ */ + /* 要素をヒープに追加 */ maxHeap.Enqueue(1, 1); maxHeap.Enqueue(3, 3); maxHeap.Enqueue(2, 2); maxHeap.Enqueue(5, 5); maxHeap.Enqueue(4, 4); - /* ヒープの先頭要素を取得 */ + /* ヒープ頂点の要素を取得 */ int peek = maxHeap.Peek();//5 - /* ヒープの先頭要素をポップ */ - // ポップされた要素は降順のシーケンスを形成 + /* ヒープ頂点の要素を取り出す */ + // 取り出された要素は大きい順の列になる peek = maxHeap.Dequeue(); // 5 peek = maxHeap.Dequeue(); // 4 peek = maxHeap.Dequeue(); // 3 @@ -181,76 +181,76 @@ /* ヒープのサイズを取得 */ int size = maxHeap.Count; - /* ヒープが空かどうかをチェック */ + /* ヒープが空かどうかを判定 */ bool isEmpty = maxHeap.Count == 0; - /* リストからヒープを作成 */ + /* 入力リストからヒープを構築 */ minHeap = new PriorityQueue([(1, 1), (3, 3), (2, 2), (5, 5), (4, 4)]); ``` === "Go" ```go title="heap.go" - // Goでは、heap.Interfaceを実装することで整数の最大ヒープを構築できます - // heap.Interfaceを実装するには、sort.Interfaceも実装する必要があります + // Go では、heap.Interface を実装することで整数の最大ヒープを構築できる + // heap.Interface を実装するには、同時に sort.Interface も実装する必要がある type intHeap []any - // heap.InterfaceのPushメソッド、要素をヒープにプッシュ + // Push は heap.Interface のメソッドで、要素をヒープに追加する func (h *intHeap) Push(x any) { - // PushとPopの両方でポインタレシーバーを使用 - // スライスの要素を調整するだけでなく、その長さも変更するため + // Push と Pop は pointer receiver を引数に取る + // スライスの内容を調整するだけでなく、スライスの長さも変更するため。 *h = append(*h, x.(int)) } - // heap.InterfaceのPopメソッド、ヒープの先頭要素を削除 + // Pop は heap.Interface のメソッドで、ヒープ頂点の要素を取り出す func (h *intHeap) Pop() any { - // ヒープからポップする要素は末尾に格納 + // 取り出す要素は末尾に格納されている last := (*h)[len(*h)-1] *h = (*h)[:len(*h)-1] return last } - // sort.InterfaceのLenメソッド + // Len は sort.Interface のメソッド func (h *intHeap) Len() int { return len(*h) } - // sort.InterfaceのLessメソッド + // Less は sort.Interface のメソッド func (h *intHeap) Less(i, j int) bool { - // 最小ヒープを実装したい場合は、これを小なり比較に変更 + // 最小ヒープを実装する場合は、不等号を小なりに変更する return (*h)[i].(int) > (*h)[j].(int) } - // sort.InterfaceのSwapメソッド + // Swap は sort.Interface のメソッド func (h *intHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } - // Top ヒープの先頭要素を取得 + // Top はヒープ頂点の要素を取得 func (h *intHeap) Top() any { return (*h)[0] } - /* ドライバーコード */ + /* Driver Code */ func TestHeap(t *testing.T) { - /* ヒープの初期化 */ - // 最大ヒープの初期化 + /* ヒープを初期化 */ + // 最大ヒープを初期化 maxHeap := &intHeap{} heap.Init(maxHeap) - /* ヒープに要素をプッシュ */ - // heap.Interfaceのメソッドを呼び出して要素を追加 + /* 要素をヒープに追加 */ + // heap.Interface のメソッドを呼び出して要素を追加する heap.Push(maxHeap, 1) heap.Push(maxHeap, 3) heap.Push(maxHeap, 2) heap.Push(maxHeap, 4) heap.Push(maxHeap, 5) - /* ヒープの先頭要素を取得 */ + /* ヒープ頂点の要素を取得 */ top := maxHeap.Top() - fmt.Printf("ヒープの先頭要素は %d\n", top) + fmt.Printf("ヒープ頂点の要素は %d\n", top) - /* ヒープの先頭要素をポップ */ - // heap.Interfaceのメソッドを呼び出して要素を削除 + /* ヒープ頂点の要素を取り出す */ + // heap.Interface のメソッドを呼び出して要素を削除する heap.Pop(maxHeap) // 5 heap.Pop(maxHeap) // 4 heap.Pop(maxHeap) // 3 @@ -261,30 +261,30 @@ size := len(*maxHeap) fmt.Printf("ヒープ内の要素数は %d\n", size) - /* ヒープが空かどうかをチェック */ + /* ヒープが空かどうかを判定 */ isEmpty := len(*maxHeap) == 0 - fmt.Printf("ヒープは空ですか? %t\n", isEmpty) + fmt.Printf("ヒープは空か %t\n", isEmpty) } ``` === "Swift" ```swift title="heap.swift" - /* ヒープの初期化 */ - // SwiftのHeap型は最大ヒープと最小ヒープの両方をサポートし、swift-collectionsライブラリが必要 + /* ヒープを初期化 */ + // Swift の Heap 型は最大ヒープと最小ヒープの両方をサポートしており、swift-collections の導入が必要 var heap = Heap() - /* ヒープに要素をプッシュ */ + /* 要素をヒープに追加 */ heap.insert(1) heap.insert(3) heap.insert(2) heap.insert(5) heap.insert(4) - /* ヒープの先頭要素を取得 */ + /* ヒープ頂点の要素を取得 */ var peek = heap.max()! - /* ヒープの先頭要素をポップ */ + /* ヒープ頂点の要素を取り出す */ peek = heap.removeMax() // 5 peek = heap.removeMax() // 4 peek = heap.removeMax() // 3 @@ -294,29 +294,29 @@ /* ヒープのサイズを取得 */ let size = heap.count - /* ヒープが空かどうかをチェック */ + /* ヒープが空かどうかを判定 */ let isEmpty = heap.isEmpty - /* リストからヒープを作成 */ + /* 入力リストからヒープを構築 */ let heap2 = Heap([1, 3, 2, 5, 4]) ``` === "JS" ```javascript title="heap.js" - // JavaScriptは組み込みのHeapクラスを提供していません + // JavaScript には組み込みの Heap クラスがない ``` === "TS" ```typescript title="heap.ts" - // TypeScriptは組み込みのHeapクラスを提供していません + // TypeScript には組み込みの Heap クラスがない ``` === "Dart" ```dart title="heap.dart" - // Dartは組み込みのHeapクラスを提供していません + // Dart には組み込みの Heap クラスがない ``` === "Rust" @@ -325,24 +325,24 @@ use std::collections::BinaryHeap; use std::cmp::Reverse; - /* ヒープの初期化 */ - // 最小ヒープの初期化 + /* ヒープを初期化 */ + // 最小ヒープを初期化 let mut min_heap = BinaryHeap::>::new(); - // 最大ヒープの初期化 + // 最大ヒープを初期化 let mut max_heap = BinaryHeap::new(); - /* ヒープに要素をプッシュ */ + /* 要素をヒープに追加 */ max_heap.push(1); max_heap.push(3); max_heap.push(2); max_heap.push(5); max_heap.push(4); - /* ヒープの先頭要素を取得 */ + /* ヒープ頂点の要素を取得 */ let peek = max_heap.peek().unwrap(); // 5 - /* ヒープの先頭要素をポップ */ - // ポップされた要素は降順のシーケンスを形成 + /* ヒープ頂点の要素を取り出す */ + // 取り出された要素は大きい順の列になる let peek = max_heap.pop().unwrap(); // 5 let peek = max_heap.pop().unwrap(); // 4 let peek = max_heap.pop().unwrap(); // 3 @@ -352,40 +352,40 @@ /* ヒープのサイズを取得 */ let size = max_heap.len(); - /* ヒープが空かどうかをチェック */ + /* ヒープが空かどうかを判定 */ let is_empty = max_heap.is_empty(); - /* リストからヒープを作成 */ + /* 入力リストからヒープを構築 */ let min_heap = BinaryHeap::from(vec![Reverse(1), Reverse(3), Reverse(2), Reverse(5), Reverse(4)]); ``` === "C" ```c title="heap.c" - // Cは組み込みのHeapクラスを提供していません + // C には組み込みの Heap クラスがない ``` === "Kotlin" ```kotlin title="heap.kt" - /* ヒープの初期化 */ - // 最小ヒープの初期化 + /* ヒープを初期化 */ + // 最小ヒープを初期化 var minHeap = PriorityQueue() - // 最大ヒープの初期化(ラムダ式でComparatorを変更するだけ) + // 最大ヒープを初期化(lambda 式で Comparator を変更すればよい) val maxHeap = PriorityQueue { a: Int, b: Int -> b - a } - /* ヒープに要素をプッシュ */ + /* 要素をヒープに追加 */ maxHeap.offer(1) maxHeap.offer(3) maxHeap.offer(2) maxHeap.offer(5) maxHeap.offer(4) - /* ヒープの先頭要素を取得 */ + /* ヒープ頂点の要素を取得 */ var peek = maxHeap.peek() // 5 - /* ヒープの先頭要素をポップ */ - // ポップされた要素は降順のシーケンスを形成 + /* ヒープ頂点の要素を取り出す */ + // 取り出された要素は大きい順の列になる peek = maxHeap.poll() // 5 peek = maxHeap.poll() // 4 peek = maxHeap.poll() // 3 @@ -395,55 +395,59 @@ /* ヒープのサイズを取得 */ val size = maxHeap.size - /* ヒープが空かどうかをチェック */ + /* ヒープが空かどうかを判定 */ val isEmpty = maxHeap.isEmpty() - /* リストからヒープを作成 */ + /* 入力リストからヒープを構築 */ minHeap = PriorityQueue(mutableListOf(1, 3, 2, 5, 4)) ``` === "Ruby" ```ruby title="heap.rb" - + # Ruby には組み込みの Heap クラスがない ``` +??? pythontutor "実行を可視化" + + https://pythontutor.com/render.html#code=import%20heapq%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20min_heap,%20flag%20%3D%20%5B%5D,%201%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20max_heap,%20flag%20%3D%20%5B%5D,%20-1%0A%20%20%20%20%0A%20%20%20%20%23%20Python%20%E7%9A%84%20heapq%20%E6%A8%A1%E5%9D%97%E9%BB%98%E8%AE%A4%E5%AE%9E%E7%8E%B0%E5%B0%8F%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E8%80%83%E8%99%91%E5%B0%86%E2%80%9C%E5%85%83%E7%B4%A0%E5%8F%96%E8%B4%9F%E2%80%9D%E5%90%8E%E5%86%8D%E5%85%A5%E5%A0%86%EF%BC%8C%E8%BF%99%E6%A0%B7%E5%B0%B1%E5%8F%AF%E4%BB%A5%E5%B0%86%E5%A4%A7%E5%B0%8F%E5%85%B3%E7%B3%BB%E9%A2%A0%E5%80%92%EF%BC%8C%E4%BB%8E%E8%80%8C%E5%AE%9E%E7%8E%B0%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%23%20%E5%9C%A8%E6%9C%AC%E7%A4%BA%E4%BE%8B%E4%B8%AD%EF%BC%8Cflag%20%3D%201%20%E6%97%B6%E5%AF%B9%E5%BA%94%E5%B0%8F%E9%A1%B6%E5%A0%86%EF%BC%8Cflag%20%3D%20-1%20%E6%97%B6%E5%AF%B9%E5%BA%94%E5%A4%A7%E9%A1%B6%E5%A0%86%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E5%A0%86%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%201%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%203%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%202%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%205%29%0A%20%20%20%20heapq.heappush%28max_heap,%20flag%20*%204%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20flag%20*%20max_heap%5B0%5D%20%23%205%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%A0%86%E9%A1%B6%E5%85%83%E7%B4%A0%E5%87%BA%E5%A0%86%0A%20%20%20%20%23%20%E5%87%BA%E5%A0%86%E5%85%83%E7%B4%A0%E4%BC%9A%E5%BD%A2%E6%88%90%E4%B8%80%E4%B8%AA%E4%BB%8E%E5%A4%A7%E5%88%B0%E5%B0%8F%E7%9A%84%E5%BA%8F%E5%88%97%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%205%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%204%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%203%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%202%0A%20%20%20%20val%20%3D%20flag%20*%20heapq.heappop%28max_heap%29%20%23%201%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%A0%86%E5%A4%A7%E5%B0%8F%0A%20%20%20%20size%20%3D%20len%28max_heap%29%0A%20%20%20%20%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%A0%86%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20not%20max_heap%0A%20%20%20%20%0A%20%20%20%20%23%20%E8%BE%93%E5%85%A5%E5%88%97%E8%A1%A8%E5%B9%B6%E5%BB%BA%E5%A0%86%0A%20%20%20%20min_heap%20%3D%20%5B1,%203,%202,%205,%204%5D%0A%20%20%20%20heapq.heapify%28min_heap%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + ## ヒープの実装 -以下の実装は最大ヒープです。最小ヒープに変換するには、すべてのサイズ論理比較を反転させるだけです(例えば、$\geq$を$\leq$に置き換える)。興味のある読者は自分で実装することをお勧めします。 +以下では最大ヒープを実装します。最小ヒープに変換したい場合は、すべての大小比較ロジックを反転させるだけです(たとえば、$\geq$ を $\leq$ に置き換えます)。興味のある読者は自分で実装してみてください。 ### ヒープの格納と表現 -「二分木」の節で述べたように、完備二分木は配列表現に非常に適しています。ヒープは完備二分木の一種なので、**配列を使用してヒープを格納します**。 +「二分木」の章で述べたように、完全二分木は配列で表現するのに非常に適しています。ヒープはまさに完全二分木の一種なので、**ここでは配列を使ってヒープを格納します**。 -配列を使用して二分木を表現する場合、要素はノード値を表し、インデックスは二分木内のノード位置を表します。**ノードポインタはインデックスマッピング公式を通じて実装されます**。 +配列で二分木を表す場合、要素はノードの値を表し、インデックスは二分木におけるノードの位置を表します。**ノード間の参照関係はインデックスの対応式によって実現できます**。 -下図に示すように、インデックス$i$が与えられた場合、その左の子のインデックスは$2i + 1$、右の子のインデックスは$2i + 2$、親のインデックスは$(i - 1) / 2$(床除算)です。インデックスが範囲外の場合、nullノードまたはノードが存在しないことを意味します。 +次の図に示すように、インデックス $i$ に対して、左子ノードのインデックスは $2i + 1$ 、右子ノードのインデックスは $2i + 2$ 、親ノードのインデックスは $(i - 1) / 2$(切り捨て除算)です。インデックスが範囲外であれば、空ノードまたはノードが存在しないことを表します。 ![ヒープの表現と格納](heap.assets/representation_of_heap.png) -後で便利に使用するため、インデックスマッピング公式を関数にカプセル化できます: +インデックスの対応式は関数にまとめておくと、後続で使いやすくなります: ```src [file]{my_heap}-[class]{max_heap}-[func]{parent} ``` -### ヒープの先頭要素へのアクセス +### ヒープ頂点の要素にアクセス -ヒープの先頭要素は二分木の根ノードで、リストの最初の要素でもあります: +ヒープ頂点の要素は二分木の根ノード、すなわちリストの先頭要素です: ```src [file]{my_heap}-[class]{max_heap}-[func]{peek} ``` -### ヒープへの要素挿入 +### 要素をヒープに追加 -要素`val`が与えられた場合、まずそれをヒープの底に追加します。追加後、`val`がヒープ内の他の要素より大きい可能性があるため、ヒープの完全性が損なわれる可能性があります。**したがって、挿入されたノードから根ノードまでのパスを修復する必要があります**。この操作はヒープ化と呼ばれます。 +与えられた要素 `val` を、まずヒープの底に追加します。追加後、`val` がヒープ内のほかの要素より大きい可能性があるため、ヒープ条件が崩れているかもしれません。**そのため、挿入ノードから根ノードまでの経路上にある各ノードを修復する必要があります**。この操作をヒープ化(heapify)と呼びます。 -挿入されたノードから開始して、**下から上にヒープ化を実行します**。下図に示すように、挿入されたノードの値をその親ノードと比較し、挿入されたノードが大きい場合はそれらを交換します。次にこの操作を続行し、根に到達するか、交換が不要なノードに遭遇するまで、下から上にヒープ内の各ノードを修復します。 +ヒープへ追加したノードから始めて、**下から上へヒープ化**を行います。次の図のように、挿入ノードとその親ノードの値を比較し、挿入ノードのほうが大きければそれらを交換します。その後もこの操作を繰り返し、下から上へ各ノードを修復して、根ノードを越えるか交換不要のノードに達した時点で終了します。 === "<1>" - ![ヒープへの要素挿入の手順](heap.assets/heap_push_step1.png) + ![要素をヒープに追加する手順](heap.assets/heap_push_step1.png) === "<2>" ![heap_push_step2](heap.assets/heap_push_step2.png) @@ -469,24 +473,24 @@ === "<9>" ![heap_push_step9](heap.assets/heap_push_step9.png) -総ノード数を$n$とすると、木の高さは$O(\log n)$です。したがって、ヒープ化操作のループ反復回数は最大$O(\log n)$で、**要素挿入操作の時間計算量は$O(\log n)$になります**。コードは以下の通りです: +ノード総数を $n$ とすると、木の高さは $O(\log n)$ です。したがって、ヒープ化操作のループ回数は高々 $O(\log n)$ であり、**要素をヒープに追加する操作の時間計算量は $O(\log n)$** です。コードは以下のとおりです: ```src [file]{my_heap}-[class]{max_heap}-[func]{sift_up} ``` -### ヒープからの先頭要素削除 +### ヒープ頂点の要素を取り出す -ヒープの先頭要素は二分木の根ノード、つまりリストの最初の要素です。リストから最初の要素を直接削除すると、二分木内のすべてのノードインデックスが変更され、後続の修復にヒープ化を使用することが困難になります。要素インデックスの変更を最小限に抑えるため、次の手順を使用します。 +ヒープ頂点の要素は二分木の根ノード、すなわちリストの先頭要素です。もし先頭要素をそのまま削除すると、二分木内のすべてのノードのインデックスが変化してしまい、その後のヒープ化による修復が困難になります。要素インデックスの変動をできるだけ小さくするため、次の手順を取ります。 -1. ヒープの先頭要素と底の要素を交換します(根ノードと最も右の葉ノードを交換)。 -2. 交換後、リストからヒープの底を削除します(交換されているため、実際には元の先頭要素が削除される)。 -3. 根ノードから開始して、**上から下にヒープ化を実行します**。 +1. ヒープ頂点の要素とヒープ底の要素を交換する(根ノードと最も右の葉ノードを交換する)。 +2. 交換後、ヒープ底をリストから削除する(すでに交換済みであるため、実際に削除されるのは元のヒープ頂点の要素であることに注意)。 +3. 根ノードから開始し、**上から下へヒープ化**を行う。 -下図に示すように、**「上から下のヒープ化」の方向は「下から上のヒープ化」と反対です**。根ノードの値をその2つの子と比較し、最大の子と交換します。次に、葉ノードに到達するか、交換が不要なノードに遭遇するまで、この操作を繰り返します。 +次の図のように、**「上から下へのヒープ化」の方向は「下から上へのヒープ化」と逆**です。根ノードの値を 2 つの子ノードと比較し、最大の子ノードと根ノードを交換します。その後、この操作を繰り返し、葉ノードを越えるか交換不要のノードに達した時点で終了します。 === "<1>" - ![ヒープからの先頭要素削除の手順](heap.assets/heap_pop_step1.png) + ![ヒープ頂点の要素を取り出す手順](heap.assets/heap_pop_step1.png) === "<2>" ![heap_pop_step2](heap.assets/heap_pop_step2.png) @@ -515,14 +519,14 @@ === "<10>" ![heap_pop_step10](heap.assets/heap_pop_step10.png) -要素挿入操作と同様に、先頭要素削除操作の時間計算量も$O(\log n)$です。コードは以下の通りです: +要素をヒープに追加する操作と同様に、ヒープ頂点の要素を取り出す操作の時間計算量も $O(\log n)$ です。コードは以下のとおりです: ```src [file]{my_heap}-[class]{max_heap}-[func]{sift_down} ``` -## ヒープの一般的な応用 +## ヒープの代表的な応用 -- **優先度キュー**:ヒープは優先度キューを実装するための好ましいデータ構造で、エンキュー操作とデキュー操作の両方の時間計算量が$O(\log n)$、キュー構築の時間計算量が$O(n)$で、すべて非常に効率的です。 -- **ヒープソート**:データセットが与えられた場合、それらからヒープを作成し、次に要素削除操作を継続的に実行して順序付けされたデータを取得できます。ただし、ヒープソートを実装するより洗練された方法があり、「ヒープソート」の章で説明されています。 -- **最大$k$個の要素の発見**:これは古典的なアルゴリズム問題であり、一般的な使用例でもあります。Weiboホット検索のトップ10ホットニュースの選択や、トップ10の売れ筋商品の選択などです。 +- **優先度付きキュー**:ヒープは、優先度付きキューを実装するための代表的なデータ構造です。キューへの追加と取り出しの時間計算量はいずれも $O(\log n)$ で、ヒープ構築は $O(n)$ であり、これらの操作はいずれも非常に効率的です。 +- **ヒープソート**:与えられたデータ群からヒープを構築し、要素の取り出しを繰り返すことで整列済みデータを得られます。ただし、通常はより洗練された方法でヒープソートを実装します。詳しくは「ヒープソート」の章を参照してください。 +- **最大の $k$ 個の要素を取得**:これは古典的なアルゴリズム問題であると同時に、典型的な応用でもあります。たとえば、人気上位 10 件のニュースをホットトピックとして選んだり、売上上位 10 件の商品を選んだりする場面です。 diff --git a/ja/docs/chapter_heap/index.md b/ja/docs/chapter_heap/index.md index 7a938ec74..c0e8ba413 100644 --- a/ja/docs/chapter_heap/index.md +++ b/ja/docs/chapter_heap/index.md @@ -4,6 +4,6 @@ !!! abstract - ヒープは山とその険しい峰のように、層をなして起伏し、それぞれが独特の形を持っています。 - - 各山の頂は散らばった高さで上下しますが、最も高いものが常に最初に注目を集めます。 + ヒープは連なる山々の峰のように、幾重にも重なり、さまざまな形をしている。 + + いくつもの山の高さはまちまちだが、最も高い峰がいつも最初に目に入る。 diff --git a/ja/docs/chapter_heap/summary.md b/ja/docs/chapter_heap/summary.md index 7dda9f17f..8ea6e4102 100644 --- a/ja/docs/chapter_heap/summary.md +++ b/ja/docs/chapter_heap/summary.md @@ -1,17 +1,17 @@ # まとめ -### 重要な復習 +### 重要なポイントの振り返り -- ヒープは完備二分木で、その構築性質に基づいて最大ヒープまたは最小ヒープに分類できます。最大ヒープの先頭要素は最大で、最小ヒープの先頭要素は最小です。 -- 優先度キューは、デキューの優先度を持つキューとして定義され、通常ヒープを使用して実装されます。 -- ヒープの一般的な操作とそれに対応する時間計算量には以下があります:ヒープへの要素挿入$O(\log n)$、ヒープからの先頭要素削除$O(\log n)$、ヒープの先頭要素へのアクセス$O(1)$。 -- 完備二分木は配列で表現するのに適しているため、ヒープは一般的に配列を使用して格納されます。 -- ヒープ化操作はヒープの性質を維持するために使用され、ヒープの挿入操作と削除操作の両方で使用されます。 -- $n$個の要素が入力として与えられた場合のヒープ構築の時間計算量は$O(n)$に最適化でき、これは非常に効率的です。 -- Top-kは古典的なアルゴリズム問題で、ヒープデータ構造を使用して効率的に解決でき、時間計算量は$O(n \log k)$です。 +- ヒープは完全二分木であり、条件の違いによって最大ヒープと最小ヒープに分けられる。最大(最小)ヒープの根の要素は最大値(最小値)である。 +- 優先度付きキューとは、取り出し時に優先度が考慮されるキューであり、通常はヒープを用いて実装される。 +- ヒープの代表的な操作とそれに対応する時間計算量には、要素の挿入 $O(\log n)$、根の要素の削除 $O(\log n)$、根の要素へのアクセス $O(1)$ などがある。 +- 完全二分木は配列で表現するのに非常に適しているため、通常は配列を使ってヒープを格納する。 +- ヒープ化操作はヒープの性質を保つために用いられ、挿入操作と削除操作の両方で使用される。 +- $n$ 個の要素を入力してヒープを構築する時間計算量は $O(n)$ まで最適化でき、非常に効率的である。 +- Top-k は古典的なアルゴリズム問題であり、ヒープ構造を用いることで効率的に解くことができ、時間計算量は $O(n \log k)$ である。 ### Q & A -**Q**: データ構造の「ヒープ」とメモリ管理の「ヒープ」は同じ概念ですか? +**Q**:データ構造の「ヒープ」とメモリ管理の「ヒープ」は同じ概念ですか? -この2つは、どちらも「ヒープ」と呼ばれますが、同じ概念ではありません。コンピュータシステムメモリのヒープは動的メモリ割り当ての一部で、プログラムが実行中にデータを格納するために使用できます。プログラムは、オブジェクトや配列などの複雑な構造を格納するために、一定量のヒープメモリを要求できます。割り当てられたデータが不要になったときは、メモリリークを防ぐためにプログラムがこのメモリを解放する必要があります。スタックメモリと比較して、ヒープメモリの管理と使用にはより多くの注意が必要で、不適切な使用はメモリリークやダングリングポインタにつながる可能性があります。 +両者は同じ概念ではなく、たまたまどちらも「ヒープ」と呼ばれているだけである。コンピュータシステムのメモリにおけるヒープは動的メモリ割り当ての一部であり、プログラムは実行時にこれを使ってデータを格納できる。プログラムは一定量のヒープメモリを要求し、オブジェクトや配列などの複雑な構造を保存できる。これらのデータが不要になったときは、メモリリークを防ぐためにそのメモリを解放する必要がある。スタックメモリと比べると、ヒープメモリの管理と使用にはより慎重さが求められ、不適切に扱うとメモリリークやダングリングポインタなどの問題を引き起こす可能性がある。 diff --git a/ja/docs/chapter_heap/top_k.md b/ja/docs/chapter_heap/top_k.md index d9e3a1ff1..e02eedbc0 100644 --- a/ja/docs/chapter_heap/top_k.md +++ b/ja/docs/chapter_heap/top_k.md @@ -1,42 +1,42 @@ -# Top-k問題 +# Top-k 問題 !!! question - 長さ$n$の順序付けられていない配列`nums`が与えられたとき、配列内の最大$k$個の要素を返してください。 + 長さ $n$ の未整列配列 `nums` が与えられたとき、配列内で最大の $k$ 個の要素を返してください。 -この問題について、まず2つの直接的な解法を紹介し、次により効率的なヒープベースの方法を説明します。 +この問題について、まずは発想が比較的直接的な 2 つの解法を紹介し、その後でより効率の高いヒープ解法を紹介します。 -## 方法1:反復選択 +## 方法一:走査による選択 -下図に示すように、$k$回の反復を実行し、各回で$1$番目、$2$番目、$\dots$、$k$番目に大きい要素を抽出できます。時間計算量は$O(nk)$です。 +以下の図に示すように $k$ 回の走査を行い、各ラウンドでそれぞれ第 $1$、$2$、$\dots$、$k$ 位の要素を取り出すことができます。時間計算量は $O(nk)$ です。 -この方法は$k \ll n$の場合にのみ適しています。$k$が$n$に近い場合、時間計算量は$O(n^2)$に近づき、非常に時間がかかります。 +この方法は $k \ll n$ の場合にしか適していません。$k$ が $n$ にかなり近いと、時間計算量は $O(n^2)$ に近づき、非常に時間がかかるためです。 -![最大k個の要素を反復的に見つける](top_k.assets/top_k_traversal.png) +![走査によって最大の k 個の要素を探す](top_k.assets/top_k_traversal.png) !!! tip - $k = n$の場合、完全に順序付けられたシーケンスを得ることができ、これは「選択ソート」アルゴリズムと同等です。 + $k = n$ のとき、完全な昇順列を得ることができ、この場合は「選択ソート」アルゴリズムと等価になります。 -## 方法2:ソート +## 方法二:ソート -下図に示すように、まず配列`nums`をソートし、次に最後の$k$個の要素を返すことができます。時間計算量は$O(n \log n)$です。 +以下の図に示すように、まず配列 `nums` をソートし、その後で右端の $k$ 個の要素を返すことができます。時間計算量は $O(n \log n)$ です。 -明らかに、この方法はタスクを「やりすぎ」ています。最大$k$個の要素を見つけるだけでよく、他の要素をソートする必要はありません。 +明らかに、この方法は必要以上の処理を行っています。なぜなら、必要なのは最大の $k$ 個の要素を見つけることだけであり、他の要素をソートする必要はないからです。 -![ソートによる最大k個の要素の発見](top_k.assets/top_k_sorting.png) +![ソートによって最大の k 個の要素を探す](top_k.assets/top_k_sorting.png) -## 方法3:ヒープ +## 方法三:ヒープ -以下のプロセスに示すように、ヒープに基づいてTop-k問題をより効率的に解決できます。 +ヒープを用いることで、Top-k 問題をより効率的に解くことができます。手順は以下の図のとおりです。 -1. 最小ヒープを初期化します。先頭要素が最小になります。 -2. まず、配列の最初の$k$個の要素をヒープに挿入します。 -3. $k + 1$番目の要素から開始し、現在の要素がヒープの先頭要素より大きい場合、ヒープの先頭要素を削除し、現在の要素をヒープに挿入します。 -4. 走査を完了した後、ヒープには最大$k$個の要素が含まれています。 +1. 最小ヒープを初期化し、そのヒープ頂点の要素が最小となるようにします。 +2. まず配列の先頭 $k$ 個の要素を順にヒープへ挿入します。 +3. $k + 1$ 番目の要素から開始し、現在の要素がヒープ頂点の要素より大きければ、ヒープ頂点の要素を取り出し、現在の要素をヒープへ挿入します。 +4. 走査が完了した後、ヒープに保持されているのが最大の $k$ 個の要素です。 === "<1>" - ![ヒープに基づく最大k個の要素の発見](top_k.assets/top_k_heap_step1.png) + ![ヒープに基づいて最大の k 個の要素を探す](top_k.assets/top_k_heap_step1.png) === "<2>" ![top_k_heap_step2](top_k.assets/top_k_heap_step2.png) @@ -62,12 +62,12 @@ === "<9>" ![top_k_heap_step9](top_k.assets/top_k_heap_step9.png) -サンプルコードは以下の通りです: +サンプルコードは以下のとおりです。 ```src [file]{top_k}-[class]{}-[func]{top_k_heap} ``` -合計$n$回のヒープ挿入と削除が実行され、最大ヒープサイズが$k$であるため、時間計算量は$O(n \log k)$です。この方法は非常に効率的で、$k$が小さい場合、時間計算量は$O(n)$に近づき、$k$が大きい場合でも、時間計算量は$O(n \log n)$を超えません。 +合計で $n$ 回のヒープ挿入と取り出しを行い、ヒープの最大長は $k$ であるため、時間計算量は $O(n \log k)$ です。この方法は非常に効率が高く、$k$ が小さいときは時間計算量が $O(n)$ に近づき、$k$ が大きいときでも $O(n \log n)$ を超えることはありません。 -さらに、この方法は動的データストリームのシナリオに適しています。データを継続的に追加することで、ヒープ内の要素を維持し、最大$k$個の要素の動的更新を実現できます。 +さらに、この方法は動的データストリームの利用シーンにも適しています。データが継続的に追加される場合でも、ヒープ内の要素を保ち続けることで、最大の $k$ 個の要素を動的に更新できます。 diff --git a/ja/docs/chapter_hello_algo/index.md b/ja/docs/chapter_hello_algo/index.md index 7f1d01291..089da2962 100644 --- a/ja/docs/chapter_hello_algo/index.md +++ b/ja/docs/chapter_hello_algo/index.md @@ -5,26 +5,26 @@ icon: material/rocket-launch-outline # はじめに -数年前、私はLeetCodeで「剣指Offer」の問題解答を共有し、多くの読者から励ましとサポートを受けました。読者とのやり取りの中で、最もよく聞かれた質問は「アルゴリズムの勉強をどう始めたらよいか」でした。次第に、私はこの質問に強い関心を抱くようになりました。 +数年前、私は LeetCode 中国版で「剣指 Offer」シリーズの解説を共有し、多くの読者から励ましと支持をいただきました。読者との交流の中で、最もよく聞かれた質問の一つが「アルゴリズムをどう学び始めればよいか」でした。次第に、私はこの問題に強い関心を抱くようになりました。 -問題を直接解くことが最も人気のある方法のようです。これはシンプルで直接的で効果的です。しかし、問題解決はマインスイーパーをプレイするようなものです。自学自習の能力が高い人は、地雷を一つずつ回避していくことができますが、しっかりとした基礎がない人は、何度もつまずいて挫折しながら後退することになるかもしれません。教科書を読むことも一般的な方法ですが、就職活動中の人にとって、卒業論文の執筆、履歴書の提出、筆記試験や面接の準備が既にエネルギーの大部分を消費しており、分厚い本を読むことはしばしば困難な挑戦となります。 +手探りでひたすら問題を解くことは、最も人気のある方法のようです。単純で、直接的で、しかも効果的です。しかし問題演習は「マインスイーパー」を遊ぶことに似ており、独学力の高い人は地雷を一つずつうまく取り除ける一方で、基礎が十分でない人は大きな痛手を受け、挫折の中で少しずつ後退してしまいがちです。教材を通読するのもよくある方法ですが、就職を目指す人にとっては、卒業論文、履歴書の提出、筆記試験や面接の準備ですでに大半の力を使い果たしており、分厚い本を読み込むことはしばしば困難な挑戦になってしまいます。 -もしあなたが同様の悩みを抱えているなら、この本があなたを見つけることができて幸運です。この本は、この質問に対する私の答えです。これが最良の解決策ではないかもしれませんが、少なくとも積極的な試みです。この本があなたに直接内定をもたらすことはできませんが、データ構造とアルゴリズムの「知識地図」を探索する手引きとなり、さまざまな「地雷」の形、大きさ、位置を理解し、さまざまな「地雷除去方法」をマスターできるようお手伝いします。これらのスキルがあれば、より快適に問題を解き、文献を読むことができ、徐々に知識体系を構築できると信じています。 +もしあなたも同じような悩みを抱えているなら、この本があなたのもとに“たどり着いた”のは幸運なことです。本書は、この問いに対して私が示す答えです。たとえ最良の解ではなくても、少なくとも前向きな試みではあります。本書だけで直接内定を得られるわけではありませんが、データ構造とアルゴリズムの「知識地図」を探る手助けをし、さまざまな「地雷」の形や大きさ、分布を理解し、いろいろな「地雷除去の方法」を身につけられるよう導きます。こうした力があれば、問題演習や文献読解をより自在に進め、やがて完整な知識体系を築いていけると信じています。 -私は、ファインマン教授の言葉に深く同感します。「知識は無料ではありません。注意を払わなければならないのです。」この意味で、この本は完全に「無料」ではありません。この本に対するあなたの貴重な「注意」に応えるために、私は最善を尽くし、最大の「注意」を払ってこの本を書きます。 +私はファインマン教授の言葉に深く賛同しています。「Knowledge isn't free. You have to pay attention.」この意味において、本書は完全に「無料」ではありません。本書のためにあなたが払ってくれる貴重な「注意」に応えるため、私はできる限りの力を尽くし、最大限の「注意」を注いで本書を書き上げます。 -自分の限界を認識しており、この本の内容が時間をかけて洗練されたにもかかわらず、間違いは確実に残っていることを理解しています。先生方や学生の皆様からの批評と訂正を心から歓迎いたします。 +私は自らの学識と力量の浅さをよく承知しています。本書の内容はある程度磨きをかけてきたものの、なお多くの誤りが残っているはずです。先生方、そして学習者の皆さまからのご批判とご指摘を心よりお願いいたします。 -![Hello Algo](../assets/covers/chapter_hello_algo.jpg){ class="cover-image" } +![Hello アルゴリズム](../assets/covers/chapter_hello_algo.jpg){ class="cover-image" }
-

Hello, Algo!

+

Hello、アルゴリズム!

-コンピュータの出現は世界に大きな変化をもたらしました。高速な計算能力と優れたプログラム可能性により、コンピュータはアルゴリズムを実行しデータを処理するための理想的な媒体となりました。ビデオゲームのリアルなグラフィックス、自動運転の知的な判断、AlphaGoの見事な囲碁ゲーム、ChatGPTの自然な対話など、これらのアプリケーションはすべて、コンピュータ上で動作するアルゴリズムの精巧な実演です。 +コンピュータの登場は世界に大きな変革をもたらしました。高速な計算能力と優れたプログラム可能性によって、アルゴリズムの実行とデータ処理の理想的な媒体となったのです。ビデオゲームのリアルな映像、自動運転の知的な意思決定、AlphaGo の見事な対局、ChatGPT の自然な対話に至るまで、これらの応用はいずれもコンピュータ上でアルゴリズムが巧みに表現されたものです。 -実際、コンピュータの出現以前から、アルゴリズムとデータ構造は世界の至る所に存在していました。初期のアルゴリズムは比較的シンプルで、古代の計数方法や道具作りの手順などがありました。文明が進歩するにつれて、アルゴリズムはより洗練され複雑になりました。職人の精巧な技術から、生産力を解放する工業製品、宇宙を支配する科学法則まで、ほぼすべての平凡または驚異的なことの背後には、アルゴリズムの巧妙な思考があります。 +実際には、コンピュータが誕生する以前から、アルゴリズムとデータ構造は世界のいたるところに存在していました。初期のアルゴリズムは比較的単純で、たとえば古代の数え方や道具作りの手順などがそれに当たります。文明の進歩とともに、アルゴリズムは次第により精緻で複雑なものになっていきました。匠の巧みな技から、生産力を解放する工業製品、さらには宇宙の運行を支配する科学法則に至るまで、ほとんどあらゆる平凡なもの、あるいは驚嘆すべきものの背後には、精妙なアルゴリズムの思想が潜んでいます。 -同様に、データ構造は至る所にあります。ソーシャルネットワークから地下鉄路線まで、多くのシステムは「グラフ」としてモデル化できます。国から家族まで、社会組織の主要な形態は「木」の特徴を示します。冬服は「スタック」のようで、最初に着たものが最後に脱がれます。バドミントンのシャトル筒は「キュー」に似ており、一方の端で挿入し、もう一方の端で取り出します。辞書は「ハッシュテーブル」のようで、目標エントリを素早く検索できます。 +同様に、データ構造も至るところに存在します。社会ネットワークのような大きなものから地下鉄路線のような小さなものまで、多くのシステムは「グラフ」としてモデル化できます。国家のような大きな単位から家庭のような小さな単位まで、社会の主な組織形態には「木」の特徴があります。冬服は「スタック」のように、最初に着たものが最後に脱がれます。バドミントンシャトルの筒は「キュー」のように、一方から入れて他方から取り出します。辞書は「ハッシュテーブル」のようなもので、目的の見出し語を素早く探せます。 -この本は、明確で理解しやすいアニメーション図解と実行可能なコード例を通じて、読者がアルゴリズムとデータ構造の核心概念を理解し、プログラミングを通じてそれらを実装できるようになることを目指しています。この基盤の上で、この本は複雑な世界におけるアルゴリズムの生き生きとした現れを明らかにし、アルゴリズムの美しさを示すことに努めています。この本があなたのお役に立てることを願っています! +本書は、わかりやすいアニメーション図解と実行可能なコード例を通じて、読者がアルゴリズムとデータ構造の核心概念を理解し、さらにプログラミングによってそれらを実装できるようになることを目指しています。そのうえで、本書は複雑な世界の中にあるアルゴリズムの生き生きとした現れを明らかにし、アルゴリズムの美しさを示そうとしています。本書があなたの助けになれば幸いです。 diff --git a/ja/docs/chapter_introduction/algorithms_are_everywhere.md b/ja/docs/chapter_introduction/algorithms_are_everywhere.md index f159f0dee..8e7e7417d 100644 --- a/ja/docs/chapter_introduction/algorithms_are_everywhere.md +++ b/ja/docs/chapter_introduction/algorithms_are_everywhere.md @@ -1,56 +1,56 @@ -# アルゴリズムは至る所にある +# アルゴリズムは至るところにある -「アルゴリズム」という言葉を聞くと、自然に数学を思い浮かべます。しかし、多くのアルゴリズムは複雑な数学を含まず、基本的な論理により多く依存しており、これは私たちの日常生活の至る所で見ることができます。 +「アルゴリズム」という言葉を聞くと、自然に数学を思い浮かべます。しかし実際には、多くのアルゴリズムは複雑な数学を必要とせず、むしろ基本的な論理に依存しており、その論理は私たちの日常生活のいたるところで見られます。 -アルゴリズムについて正式に議論を始める前に、興味深い事実を共有する価値があります。**あなたは無意識のうちに多くのアルゴリズムを学び、日常生活でそれらを応用することに慣れています**。ここで、この点を証明するためにいくつかの具体的な例を挙げます。 +アルゴリズムを本格的に議論する前に、ひとつ面白い事実を共有しておきます。**あなたはすでに知らず知らずのうちに多くのアルゴリズムを身につけ、それらを日常生活に応用することに慣れているのです**。以下では、いくつかの具体例を挙げてこれを示します。 -**例1:辞書の引き方**。英語の辞書では、単語がアルファベット順に並んでいます。$r$で始まる単語を探していると仮定すると、通常は以下の方法で行います: +**例1:辞書を引く**。辞書では、各漢字に対応するピンインがあり、辞書はピンインのアルファベット順に並んでいます。ピンインの先頭文字が $r$ の字を探すと仮定すると、通常は次の図のような方法で行います。 -1. 辞書を大体半分ぐらいのところで開き、そのページの最初の語彙を確認します。例えば$m$で始まる文字だとしましょう。 -2. $r$はアルファベットで$m$の後に来るので、前半を無視して、探索空間を後半に絞ります。 -3. $r$で始まる単語を見つけるまで、ステップ`1.`と`2.`を繰り返します。 +1. 辞書をおよそ半分のところまで開き、そのページの先頭文字を確認し、先頭文字が $m$ だとします。 +2. ピンインのアルファベット表では $r$ は $m$ の後にあるため、辞書の前半を除外し、探索範囲を後半に絞ります。 +3. ピンインの先頭文字が $r$ のページを見つけるまで、手順 `1.` と手順 `2.` を繰り返します。 === "<1>" - ![辞書を引く過程](algorithms_are_everywhere.assets/binary_search_dictionary_step1.png) + ![辞書を引く手順](algorithms_are_everywhere.assets/binary_search_dictionary_step1.png) === "<2>" - ![辞書での二分探索ステップ2](algorithms_are_everywhere.assets/binary_search_dictionary_step2.png) + ![binary_search_dictionary_step2](algorithms_are_everywhere.assets/binary_search_dictionary_step2.png) === "<3>" - ![辞書での二分探索ステップ3](algorithms_are_everywhere.assets/binary_search_dictionary_step3.png) + ![binary_search_dictionary_step3](algorithms_are_everywhere.assets/binary_search_dictionary_step3.png) === "<4>" - ![辞書での二分探索ステップ4](algorithms_are_everywhere.assets/binary_search_dictionary_step4.png) + ![binary_search_dictionary_step4](algorithms_are_everywhere.assets/binary_search_dictionary_step4.png) === "<5>" - ![辞書での二分探索ステップ5](algorithms_are_everywhere.assets/binary_search_dictionary_step5.png) + ![binary_search_dictionary_step5](algorithms_are_everywhere.assets/binary_search_dictionary_step5.png) -辞書を引くことは、小学生にとって必須のスキルですが、実際には有名な「二分探索」アルゴリズムです。データ構造の観点から、辞書をソートされた「配列」と考えることができます。アルゴリズムの観点から、辞書で単語を探すために取られる一連の行動は、「二分探索」アルゴリズムと見なすことができます。 +辞書を引くという小学生の必須スキルは、実は有名な「二分探索」アルゴリズムそのものです。データ構造の観点では、辞書を整列済みの「配列」とみなせます。アルゴリズムの観点では、上記の一連の辞書引きの操作を「二分探索」とみなせます。 -**例2:トランプの整理**。トランプをプレイするとき、手札を昇順に並べる必要があります。以下の過程で示されます。 +**例2:トランプを整理する**。カードゲームをするとき、毎回手札のトランプを小さい順に並べ替える必要があります。その流れは次の図のとおりです。 -1. トランプを「整列済み」と「未整列」のセクションに分けます。最初は一番左のカードが既に整列していると仮定します。 -2. 未整列セクションからカードを1枚取り出し、整列済みセクションの正しい位置に挿入します。この後、左端の2枚のカードが整列します。 -3. すべてのカードが整列するまで、ステップ`2`を繰り返します。 +1. トランプを「整列済み」と「未整列」の2つの部分に分け、初期状態では一番左の1枚がすでに整列済みだとします。 +2. 未整列部分から1枚のトランプを取り出し、整列済み部分の正しい位置に挿入します。完了すると、左端の2枚は整列済みになります。 +3. 手順 `2.` を繰り返し、各ラウンドで未整列部分から1枚を整列済み部分へ挿入し、すべてのトランプが整列済みになるまで続けます。 -![トランプの整理過程](algorithms_are_everywhere.assets/playing_cards_sorting.png) +![トランプを並べ替える手順](algorithms_are_everywhere.assets/playing_cards_sorting.png) -上記のトランプを整理する方法は、実質的に「挿入ソート」アルゴリズムであり、小さなデータセットに対して非常に効率的です。多くのプログラミング言語のソート関数には挿入ソートが含まれています。 +上記のトランプ整理の方法は、本質的には「挿入ソート」アルゴリズムです。これは小規模なデータ集合を処理する際に非常に効率的で、多くのプログラミング言語のソートライブラリ関数にも挿入ソートが使われています。 -**例3:お釣りの計算**。スーパーマーケットで$69$の買い物をしたと仮定します。レジ係に$100$を渡すと、$31$のお釣りを提供する必要があります。この過程は以下の図で明確に理解できます。 +**例3:お釣りを出す**。スーパーで $69$ 元の商品を購入し、店員に $100$ 元渡したとすると、店員は $31$ 元のお釣りを返す必要があります。店員は自然に次の図のような考え方をします。 -1. 選択肢は$31$以下の価値のある通貨で、$1$、$5$、$10$、$20$が含まれます。 -2. 選択肢から最大の$20$を取り出し、$31 - 20 = 11$が残ります。 -3. 残りの選択肢から最大の$10$を取り出し、$11 - 10 = 1$が残ります。 -4. 残りの選択肢から最大の$1$を取り出し、$1 - 1 = 0$が残ります。 -5. お釣りの計算が完了し、解答は$20 + 10 + 1 = 31$です。 +1. 選択肢は $31$ 元より小さい額面の貨幣で、$1$ 元、$5$ 元、$10$ 元、$20$ 元があります。 +2. 選択肢の中から最大の $20$ 元を取り出すと、残りは $31 - 20 = 11$ 元です。 +3. 残りの選択肢の中から最大の $10$ 元を取り出すと、残りは $11 - 10 = 1$ 元です。 +4. 残りの選択肢の中から最大の $1$ 元を取り出すと、残りは $1 - 1 = 0$ 元です。 +5. お釣りは完了し、内訳は $20 + 10 + 1 = 31$ 元です。 -![お釣りの計算過程](algorithms_are_everywhere.assets/greedy_change.png) +![お釣りの過程](algorithms_are_everywhere.assets/greedy_change.png) -記述されたステップでは、利用可能な最大の額面を使用して各段階で最良の選択肢を選ぶことで、効果的なお釣り計算戦略につながります。データ構造とアルゴリズムの観点から、このアプローチは「貪欲」アルゴリズムとして知られています。 +以上の手順では、各ステップでその時点で最善と思われる選択肢を取っています。つまり、できるだけ額面の大きい貨幣を使い、最終的に実行可能なお釣りの方案を得ています。データ構造とアルゴリズムの観点から見ると、この方法は本質的に「貪欲法」です。 -料理の準備から宇宙旅行まで、ほぼすべての問題解決にはアルゴリズムが関わっています。コンピュータの出現により、メモリにデータ構造を格納し、CPUとGPUを呼び出してアルゴリズムを実行するコードを書くことができるようになりました。このようにして、現実世界の問題をコンピュータに移し、より効率的な方法でさまざまな複雑な問題を解決できます。 +料理を一品作ることから星間航行に至るまで、ほとんどあらゆる問題の解決にアルゴリズムは欠かせません。コンピュータの登場によって、プログラミングを通じてデータ構造をメモリに格納し、さらにコードを書いて CPU や GPU にアルゴリズムを実行させることが可能になりました。こうして、生活の中の問題をコンピュータに移し、より効率的な方法でさまざまな複雑な問題を解決できるのです。 !!! tip - データ構造、アルゴリズム、配列、二分探索などの概念についてまだ混乱している場合は、読み続けることをお勧めします。この本は、データ構造とアルゴリズムの理解の領域へと優しく導いてくれるでしょう。 + データ構造、アルゴリズム、配列、二分探索といった概念がまだ少し曖昧でも、そのまま読み進めてください。本書がデータ構造とアルゴリズムの知識の世界へと案内します。 diff --git a/ja/docs/chapter_introduction/index.md b/ja/docs/chapter_introduction/index.md index be3840fe8..04c9d61ec 100644 --- a/ja/docs/chapter_introduction/index.md +++ b/ja/docs/chapter_introduction/index.md @@ -1,9 +1,9 @@ -# アルゴリズムとの出会い +# アルゴリズム入門 -![アルゴリズムとの出会い](../assets/covers/chapter_introduction.jpg) +![アルゴリズム入門](../assets/covers/chapter_introduction.jpg) !!! abstract - 優雅な乙女が踊ります。データと絡み合い、アルゴリズムのメロディーに合わせてスカートをなびかせながら。 - - 彼女があなたをダンスに誘います。彼女のステップに従って、論理と美に満ちたアルゴリズムの世界に入りましょう。 + 一人の少女が軽やかに舞い、データと織り重なり合いながら、スカートの裾にはアルゴリズムの旋律がたなびいています。 + + 彼女はあなたをこの舞いへと誘います。その足取りに続いて、論理と美しさに満ちたアルゴリズムの世界へ踏み入りましょう。 diff --git a/ja/docs/chapter_introduction/summary.md b/ja/docs/chapter_introduction/summary.md index cec45ab46..5e482a185 100644 --- a/ja/docs/chapter_introduction/summary.md +++ b/ja/docs/chapter_introduction/summary.md @@ -1,22 +1,24 @@ # まとめ -- アルゴリズムは日常生活にありふれており、思っているほどアクセスしにくく複雑なものではありません。実際、私たちは既に無意識のうちに多くのアルゴリズムを学び、生活の様々な問題を解決するために使用しています。 -- 辞書で単語を引く原理は二分探索アルゴリズムと一致しています。二分探索アルゴリズムは分割統治という重要なアルゴリズム概念を体現しています。 -- トランプを整理する過程は挿入ソートアルゴリズムと非常に似ています。挿入ソートアルゴリズムは小さなデータセットのソートに適しています。 -- 通貨でお釣りを計算するステップは本質的に貪欲アルゴリズムに従っており、各ステップでその時点での最良の選択をします。 -- アルゴリズムは有限時間内で特定の問題を解決するための段階的な指示のセットですが、データ構造はコンピュータでのデータの組織化と保存方法を定義します。 -- データ構造とアルゴリズムは密接に関連しています。データ構造はアルゴリズムの基礎であり、アルゴリズムはデータ構造の機能を活用するステージです。 -- データ構造とアルゴリズムをブロックの組み立てに例えることができます。ブロックはデータを表し、ブロックの形状と接続方法はデータ構造を表し、ブロックを組み立てるステップはアルゴリズムに対応します。 +### 要点の振り返り + +- アルゴリズムは日常生活の至る所にあり、決して手の届かない難解な知識ではありません。実際、私たちは気づかないうちに多くのアルゴリズムを身につけ、生活のさまざまな問題を解決しています。 +- 辞書を引く原理は二分探索アルゴリズムと一致しています。二分探索アルゴリズムは分割統治という重要なアルゴリズム思想を体現しています。 +- トランプを整理する過程は挿入ソートアルゴリズムと非常によく似ています。挿入ソートアルゴリズムは小規模なデータ集合のソートに適しています。 +- 貨幣の釣り銭を求める手順の本質は貪欲アルゴリズムであり、各ステップでその時点で最善と思われる選択を取ります。 +- アルゴリズムとは、限られた時間内に特定の問題を解決するための一連の命令または操作手順であり、データ構造とは、コンピュータ内でデータを組織し保存する方法です。 +- データ構造とアルゴリズムは密接に結びついています。データ構造はアルゴリズムの土台であり、アルゴリズムはデータ構造に生命を吹き込みます。 +- データ構造とアルゴリズムは積み木の組み立てにたとえることができます。積み木はデータを表し、積み木の形や接続方法などはデータ構造を表し、積み木を組み立てる手順がアルゴリズムに対応します。 ### Q & A -**Q**:プログラマーとして、日常の仕事でアルゴリズムを手動で実装する必要があることはめったにありません。最も一般的に使用されるアルゴリズムは、既にプログラミング言語とライブラリに組み込まれており、すぐに使用できます。これは、私たちが仕事で遭遇する問題が、カスタムアルゴリズム設計を必要とする複雑さのレベルにまだ達していないことを示唆していますか? +**Q**:プログラマーとして、私は日常業務でアルゴリズムを使って問題を解決したことがありません。よく使うアルゴリズムはプログラミング言語にすべてカプセル化されており、そのまま使えばよいです。これは、仕事上の問題がまだアルゴリズムを必要とする段階に達していないことを意味するのでしょうか? -特定の仕事スキルが武術の「技」のようなものだとすれば、基礎科目は「内功」のようなものです。 +具体的な仕事のスキルを武術の「型」にたとえるなら、基礎科目はむしろ「内功」に近いものです。 -アルゴリズム(およびその他の基礎科目)を学ぶ意義は、必ずしも仕事でそれらを一から実装することではなく、概念の確固たる理解に基づいて、より専門的な意思決定と問題解決を可能にし、それによって仕事の全体的な質を向上させることだと私は信じています。例えば、すべてのプログラミング言語には組み込みのソート関数があります: +私は、アルゴリズム(およびその他の基礎科目)を学ぶ意義は、仕事でそれをゼロから実装することではなく、学んだ知識に基づいて問題解決の際に専門的な反応や判断を下せるようになり、その結果として仕事全体の品質を高めることにあると考えています。簡単な例を挙げると、どのプログラミング言語にもソート関数が組み込まれています。 -- データ構造とアルゴリズムを学んでいない場合、どんなデータが与えられても、このソート関数に渡すだけかもしれません。スムーズに動作し、良いパフォーマンスを示し、問題がないように見えます。 -- しかし、アルゴリズムを学んだことがあれば、組み込みのソート関数の時間複雑度は通常$O(n \log n)$であることを理解しています。さらに、データが固定桁数の整数(学生IDなど)で構成されている場合、基数ソートのようなより効率的なアプローチを適用でき、時間複雑度をO(nk)に削減できます。ここでkは桁数です。大量のデータを処理する際、節約された時間は重要な価値に変わります — コストの削減、ユーザーエクスペリエンスの向上、システムパフォーマンスの向上。 +- もしデータ構造とアルゴリズムを学んでいなければ、どんなデータが与えられても、そのソート関数に任せてしまうかもしれません。問題なく動き、性能も悪くなく、一見すると特に問題はありません。 +- しかしアルゴリズムを学んでいれば、組み込みのソート関数の時間計算量が $O(n \log n)$ であることを知っています。さらに、与えられたデータが固定桁数の整数(例えば学籍番号)であれば、より効率の高い「基数ソート」を使って、時間計算量を $O(nk)$ に下げることができます。ここで $k$ は桁数です。データ量が非常に大きい場合、節約できた実行時間は大きな価値を生みます(コスト削減、体験向上など)。 -エンジニアリングでは、多くの問題を最適に解決することは困難です。ほとんどは「準最適」解決策で対処されます。問題の難しさは、その固有の複雑さだけでなく、それに取り組む人の知識と経験にも依存します。専門知識と経験が深いほど、分析がより徹底的になり、問題をより優雅に解決できます。 +工学分野では、多くの問題で最適解に到達することは難しく、少なくない問題は「だいたい」解決されているにすぎません。問題の難しさは、一方では問題そのものの性質に依存し、他方ではそれを観測する人の知識の蓄積にも依存します。知識が充実し、経験が豊富であるほど、問題分析はより深くなり、問題はより洗練された形で解決できるようになります。 diff --git a/ja/docs/chapter_introduction/what_is_dsa.md b/ja/docs/chapter_introduction/what_is_dsa.md index d42d92f41..54944e3f8 100644 --- a/ja/docs/chapter_introduction/what_is_dsa.md +++ b/ja/docs/chapter_introduction/what_is_dsa.md @@ -1,53 +1,53 @@ -# アルゴリズムとは何か +# アルゴリズムとは ## アルゴリズムの定義 -アルゴリズムは、有限時間内で特定の問題を解決するための一連の指示またはステップです。以下の特徴があります: +アルゴリズム(algorithm)とは、限られた時間内に特定の問題を解決するための一連の命令または操作手順であり、次のような特徴を持ちます。 -- 問題が明確に定義されており、入力と出力の明確な定義が含まれています。 -- アルゴリズムは実行可能で、有限の回数のステップ、時間、メモリ空間内で完了できることを意味します。 -- 各ステップには明確な意味があります。同じ入力と条件の下で出力は一貫して同じです。 +- 問題が明確であり、入力と出力の定義がはっきりしています。 +- 実行可能であり、有限の手順、時間、メモリ空間で完了できます。 +- 各手順の意味が確定しており、同じ入力と実行条件では常に同じ出力になります。 ## データ構造の定義 -データ構造は、コンピュータ内でデータを組織し保存する方法で、以下の設計目標があります: +データ構造(data structure)とは、データを整理して保存する方式であり、データの内容、データ間の関係、データの操作方法を含み、次のような設計目標があります。 -- コンピュータのメモリを節約するために空間占有を最小化する。 -- データ操作を可能な限り高速にし、データのアクセス、追加、削除、更新などをカバーする。 -- 効率的なアルゴリズム実行を可能にするために、簡潔なデータ表現と論理情報を提供する。 +- 使用する空間をできるだけ少なくし、コンピュータのメモリを節約します。 +- データの操作をできるだけ高速にし、アクセス、追加、削除、更新などを含みます。 +- 簡潔なデータ表現と論理情報を提供し、アルゴリズムが効率よく動作できるようにします。 -**データ構造の設計はバランスを取る行為であり、しばしばトレードオフが必要です**。一つの側面を改善したい場合、しばしば別の側面で妥協する必要があります。以下は2つの例です: +**データ構造の設計はトレードオフに満ちた過程です**。ある面を改善したい場合、別の面で妥協が必要になることがよくあります。以下に 2 つの例を示します。 -- 配列と比較して、連結リストはデータの追加と削除においてより便利ですが、データアクセス速度を犠牲にします。 -- 連結リストと比較して、グラフはより豊富な論理情報を提供しますが、より多くのメモリ空間が必要です。 +- 連結リストは配列に比べてデータの追加や削除がしやすい一方で、データアクセス速度を犠牲にしています。 +- グラフは連結リストに比べてより豊富な論理情報を提供しますが、より大きなメモリ空間を必要とします。 ## データ構造とアルゴリズムの関係 -以下の図に示すように、データ構造とアルゴリズムは高度に関連し、密接に統合されており、具体的には以下の3つの側面があります: +以下の図のように、データ構造とアルゴリズムは高度に関連し、密接に結び付いており、具体的には次の 3 つの点に表れます。 -- データ構造はアルゴリズムの基礎です。構造化されたデータ保存とアルゴリズムのためのデータ操作方法を提供します。 -- アルゴリズムはデータ構造に活力を注入します。データ構造だけではデータ情報を保存するだけです。アルゴリズムの応用によって、特定の問題を解決できます。 -- アルゴリズムは異なるデータ構造に基づいて実装できることが多いですが、実行効率は大きく異なることがあります。適切なデータ構造を選択することが鍵です。 +- データ構造はアルゴリズムの土台です。データ構造はアルゴリズムに対して、構造化して格納されたデータと、そのデータを操作する方法を提供します。 +- アルゴリズムはデータ構造に命を吹き込みます。データ構造そのものはデータ情報を保存するだけであり、アルゴリズムと組み合わせて初めて特定の問題を解決できます。 +- アルゴリズムは通常、異なるデータ構造に基づいて実装できますが、実行効率が大きく異なる場合があり、適切なデータ構造を選ぶことが重要です。 ![データ構造とアルゴリズムの関係](what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png) -データ構造とアルゴリズムは、以下の図に示すように、ブロックのセットに例えることができます。ブロックセットには多数のピースが含まれ、詳細な組み立て説明書が付いています。これらの説明書に段階的に従うことで、複雑なブロックモデルを構築できます。 +データ構造とアルゴリズムは、以下の図に示す組み立てブロックのようなものです。1 セットのブロックには多くの部品が含まれるだけでなく、詳しい組み立て説明書も付いています。説明書に従って一歩ずつ操作すれば、精巧なブロック模型を組み立てられます。 -![ブロックの組み立て](what_is_dsa.assets/assembling_blocks.png) +![組み立てブロック](what_is_dsa.assets/assembling_blocks.png) -両者の詳細な対応関係は以下の表に示されています。 +両者の詳細な対応関係を次の表に示します。 -

  データ構造とアルゴリズムをブロックと比較

+

  データ構造とアルゴリズムを組み立てブロックにたとえる

-| データ構造とアルゴリズム | ブロック | -| ------------------------ | ----------------------------------------------------- | -| 入力データ | 未組み立てのブロック | -| データ構造 | ブロックの組織、形状、サイズ、接続などを含む | -| アルゴリズム | ブロックを望ましい形状に組み立てる一連のステップ | -| 出力データ | 完成したブロックモデル | +| データ構造とアルゴリズム | 組み立てブロック | +| -------------- | ---------------------------------------- | +| 入力データ | まだ組み立てていないブロック | +| データ構造 | ブロックの構成形式。形状、大きさ、接続方法などを含む | +| アルゴリズム | ブロックを目標の形に組み上げる一連の操作手順 | +| 出力データ | ブロック模型 | -データ構造とアルゴリズムはプログラミング言語から独立していることは注目に値します。この理由により、この本は複数のプログラミング言語での実装を提供できます。 +特筆すべき点として、データ構造とアルゴリズムはプログラミング言語から独立しています。だからこそ、本書では複数のプログラミング言語に基づく実装を提供できます。 -!!! tip "慣習的な略語" +!!! tip "慣習的な略称" - 実生活の議論では、「データ構造とアルゴリズム」を単純に「アルゴリズム」と呼ぶことがよくあります。例えば、よく知られたLeetCodeアルゴリズム問題は、実際にはデータ構造とアルゴリズムの両方の知識をテストしています。 + 実際の議論では、私たちは通常「データ構造とアルゴリズム」を略して「アルゴリズム」と呼びます。たとえば広く知られている LeetCode のアルゴリズム問題は、実際にはデータ構造とアルゴリズムの両方の知識を同時に問うています。 diff --git a/ja/docs/chapter_paperbook/index.md b/ja/docs/chapter_paperbook/index.md new file mode 100644 index 000000000..efc58cbde --- /dev/null +++ b/ja/docs/chapter_paperbook/index.md @@ -0,0 +1,68 @@ +--- +comments: true +icon: material/book-open-page-variant +status: new +--- + +# 紙の書籍 + +長い時間をかけて磨き上げた『Hello アルゴリズム』の紙の書籍が、ついに発売されました!今の気持ちは、次の一節で表せます: + +

風を追い月を追って立ち止まるな、草原の果てには春の山がある。

+ +![](index.assets/paper_book_overview.jpg){ class="animation-figure" } + +以下の動画では紙の書籍を紹介しており、私の考えもいくつか含まれています: + +- データ構造とアルゴリズムを学ぶ重要性。 +- なぜ紙の書籍で Python を選んだのか。 +- 知識共有に対する理解。 + +> 新人 UP 主ですので、ぜひ応援と高評価・チャンネル登録をお願いします~ありがとうございます! + +
+ +
+ +紙の書籍のスナップショット: + +![](index.assets/paper_book_chapter_heap.jpg){ class="animation-figure" } + +![](index.assets/paper_book_avl_tree.jpg){ class="animation-figure" } + +## 長所と短所 + +紙の書籍ならではの魅力を、簡単にまとめると次のとおりです: + +- フルカラー印刷を採用し、本書の「アニメーション図解」の強みをそのまま活かせます。 +- 紙の素材にもこだわり、色彩を高い精度で再現しつつ、紙の書籍ならではの質感も残しています。 +- 紙の書籍版は Web 版よりも書式が整っており、たとえば図中の数式には斜体を用いています。 +- 価格を上げずに、マインドマップの折り込みページやしおりも付属します。 +- 紙の書籍、Web 版、PDF 版で内容は同期しており、自由に切り替えて読めます。 + +!!! tip + + 紙の書籍と Web 版を同期させるのは難しいため、細かな違いが生じる場合があります。ご了承ください! + +もちろん、購入前に検討しておくべき点もいくつかあります: + +- Python 言語を使用しているため、あなたの主言語と合わない可能性があります(Python は疑似コードと捉え、考え方の理解を重視してください)。 +- フルカラー印刷は図解やコードの読みやすさを大きく高める一方で、白黒印刷より価格はやや高くなります。 + +!!! tip + + 「印刷品質」と「価格」は、アルゴリズムにおける「時間効率」と「空間効率」のようなもので、両立は容易ではありません。そして私は、「印刷品質」は「時間効率」に当たるため、より重視すべきだと考えています。 + +## 購入リンク + +紙の書籍に興味があれば、ぜひ一冊ご検討ください。新刊の 5 割引を用意していただきましたので、[こちらのリンク](https://3.cn/1X-qmTD3)をご覧いただくか、以下の QR コードをスキャンしてください: + +![](index.assets/book_jd_link.jpg){ class="animation-figure" } + +## あとがき + +当初、私は紙の書籍出版に必要な作業量を甘く見ていて、オープンソースプロジェクトをきちんと保守していれば、紙の書籍版も何らかの自動化手段で生成できると思っていました。実際には、紙の書籍の制作フローとオープンソースプロジェクトの更新の仕組みには大きな違いがあり、その間をつなぐには多くの追加作業が必要でした。 + +一冊の本の初稿と出版基準を満たす完成稿との間には、なお大きな隔たりがあります。出版社(企画、編集、デザイン、マーケティングなど)と著者が力を合わせ、長い時間をかけて磨き上げていく必要があります。ここで、図霊の企画編集者である王軍花さん、そして人民郵電出版社と図霊コミュニティで本書の出版工程に携わってくださったすべての皆さまに感謝いたします! + +この本があなたの助けになれば幸いです! diff --git a/ja/docs/chapter_preface/about_the_book.md b/ja/docs/chapter_preface/about_the_book.md index 8c2a52bcc..b671987f2 100644 --- a/ja/docs/chapter_preface/about_the_book.md +++ b/ja/docs/chapter_preface/about_the_book.md @@ -1,52 +1,54 @@ -# この本について +# 本書について -このオープンソースプロジェクトは、データ構造とアルゴリズムに関する無料で初心者にやさしいクラッシュコースの作成を目指しています。 +本プロジェクトは、オープンソースで無料、かつ初心者にやさしいデータ構造とアルゴリズムの入門書を作ることを目的としています。 -- アニメーション付きの図解、理解しやすい内容、滑らかな学習曲線により、初心者がデータ構造とアルゴリズムの「知識マップ」を探索するのに役立ちます。 -- ワンクリックでコードを実行できるため、読者のプログラミングスキルの向上と、アルゴリズムの動作原理およびデータ構造の基礎実装の理解に役立ちます。 -- 教えることによる学習を促進し、質問や洞察の共有を自由に行ってください。議論を通じて一緒に成長しましょう。 +- 全編でアニメーション付きの図解を採用し、内容は明快で理解しやすく、学習曲線もなだらかで、初心者がデータ構造とアルゴリズムの知識地図を探求できるよう導きます。 +- ソースコードはワンクリックで実行でき、読者が演習を通じてプログラミング能力を高め、アルゴリズムの動作原理とデータ構造の内部実装を理解する助けとなります。 +- 読者どうしの助け合いによる学習を推奨しており、コメント欄で質問や見解を共有し、対話と議論を通じてともに成長していくことを歓迎します。 ## 対象読者 -もしあなたがアルゴリズムに触れたばかりで経験が限られている場合、またはアルゴリズムである程度の経験を積んでいても、データ構造とアルゴリズムについて曖昧な理解しかなく、常に「分かった」と「うーん」の間を行き来している場合、この本はあなたのためのものです! +もしあなたがアルゴリズム初心者で、これまでアルゴリズムに触れたことがない、あるいはすでに多少の問題演習の経験はあるものの、データ構造とアルゴリズムについてはまだ曖昧な理解にとどまり、できるかできないかの間を行き来しているなら、本書はまさにあなたのために作られています! -すでにある程度の問題解決経験を積んでおり、ほとんどのタイプの問題に精通している場合、この本はアルゴリズムの知識体系を復習し整理するのに役立ちます。リポジトリのソースコードは「問題解決ツールキット」や「アルゴリズムチートシート」として使用できます。 +もしすでに一定量の問題演習を積み、ほとんどの問題パターンに慣れているなら、本書はアルゴリズム知識体系の復習と整理に役立ちます。リポジトリのソースコードは「問題演習ツール集」や「アルゴリズム辞典」として活用できます。 -もしあなたがアルゴリズムの専門家であれば、貴重な提案をいただくか、[参加して協力](https://www.hello-algo.com/chapter_appendix/contribution/)していただければと思います。 +もしあなたがアルゴリズムの「達人」なら、貴重なご提案をいただけることを楽しみにしています。あるいは[一緒に執筆に参加](https://www.hello-algo.com/chapter_appendix/contribution/)してください。 !!! success "前提条件" - 少なくとも一つのプログラミング言語で簡単なコードを書いて読むことができる必要があります。 + 少なくともいずれか一つの言語でのプログラミング基礎があり、簡単なコードを読んだり書いたりできる必要があります。 ## 内容構成 -本書の主な内容を下図に示します。 +本書の主な内容は以下の図のとおりです。 -- **計算量解析**: データ構造とアルゴリズムを評価する側面と方法を探求します。時間計算量と空間計算量を導出する方法、および一般的なタイプと例を扱います。 -- **データ構造**: 基本的なデータ型、分類方法、定義、長所と短所、一般的な操作、タイプ、応用、および配列、連結リスト、スタック、キュー、ハッシュテーブル、木、ヒープ、グラフなどのデータ構造の実装方法に焦点を当てます。 -- **アルゴリズム**: アルゴリズムを定義し、その長所と短所、効率性、応用シナリオ、問題解決ステップについて議論し、検索、ソート、分割統治、バックトラッキング、動的プログラミング、貪欲アルゴリズムなど、さまざまなアルゴリズムのサンプル問題を含みます。 +- **計算量解析**:データ構造とアルゴリズムを評価する観点と方法。時間計算量と空間計算量の求め方、代表的な種類、例など。 +- **データ構造**:基本データ型とデータ構造の分類方法。配列、連結リスト、スタック、キュー、ハッシュテーブル、木、ヒープ、グラフなどのデータ構造の定義、長所と短所、基本操作、代表的な種類、典型的な応用、実装方法など。 +- **アルゴリズム**:探索、ソート、分割統治、バックトラッキング、動的計画法、貪欲法などのアルゴリズムの定義、長所と短所、効率、適用場面、問題を解く手順、例題など。 ![本書の主な内容](about_the_book.assets/hello_algo_mindmap.png) ## 謝辞 -本書は、オープンソースコミュニティの多数の貢献者による共同の努力のもと、継続的に改善されています。時間と労力を注いで執筆に携わってくださったすべての方々に感謝します。貢献者は以下のとおりです(GitHub により自動生成された順序):krahets、coderonion、Gonglja、nuomi1、Reanon、justin-tse、hpstory、danielsss、curtishd、night-cruise、S-N-O-R-L-A-X、rongyi、msk397、gvenusleo、khoaxuantu、rivertwilight、K3v123、gyt95、zhuoqinyue、yuelinxin、Zuoxun、mingXta、Phoenix0415、FangYuan33、GN-Yu、longsizhuo、IsChristina、xBLACKICEx、guowei-gong、Cathay-Chen、pengchzn、QiLOL、magentaqin、hello-ikun、JoseHung、qualifier1024、thomasq0、sunshinesDL、L-Super、Guanngxu、Transmigration-zhou、WSL0809、Slone123c、lhxsm、yuan0221、what-is-me、Shyam-Chen、theNefelibatas、longranger2、codeberg-user、xiongsp、JeffersonHuang、prinpal、seven1240、Wonderdch、malone6、xiaomiusa87、gaofer、bluebean-cloud、a16su、SamJin98、hongyun-robot、nanlei、XiaChuerwu、yd-j、iron-irax、mgisr、steventimes、junminhong、heshuyue、danny900714、MolDuM、Nigh、Dr-XYZ、XC-Zero、reeswell、PXG-XPG、NI-SW、Horbin-Magician、Enlightenus、YangXuanyi、beatrix-chan、DullSword、xjr7670、jiaxianhua、qq909244296、iStig、boloboloda、hts0000、gledfish、wenjianmin、keshida、kilikilikid、lclc6、lwbaptx、linyejoe2、liuxjerry、llql1211、fbigm、echo1937、szu17dmy、dshlstarr、Yucao-cy、coderlef、czruby、bongbongbakudan、beintentional、ZongYangL、ZhongYuuu、ZhongGuanbin、hezhizhen、linzeyan、ZJKung、luluxia、xb534、ztkuaikuai、yw-1021、ElaBosak233、baagod、zhouLion、yishangzhang、yi427、yanedie、yabo083、weibk、wangwang105、th1nk3r-ing、tao363、4yDX3906、syd168、sslmj2020、smilelsb、siqyka、selear、sdshaoda、Xi-Row、popozhu、nuquist19、noobcodemaker、XiaoK29、chadyi、lyl625760、lucaswangdev、0130w、shanghai-Jerry、EJackYang、Javesun99、eltociear、lipusheng、KNChiu、BlindTerran、ShiMaRing、lovelock、FreddieLi、FloranceYeh、fanchenggang、gltianwen、goerll、nedchu、curly210102、CuB3y0nd、KraHsu、CarrotDLaw、youshaoXG、bubble9um、Asashishi、Asa0oo0o0o、fanenr、eagleanurag、akshiterate、52coder、foursevenlove、KorsChen、GaochaoZhu、hopkings2008、yang-le、realwujing、Evilrabbit520、Umer-Jahangir、Turing-1024-Lee、Suremotoo、paoxiaomooo、Chieko-Seren、Allen-Scai、ymmmas、Risuntsy、Richard-Zhang1019、RafaelCaso、qingpeng9802、primexiao、Urbaner3、zhongfq、nidhoggfgg、MwumLi、CreatorMetaSky、martinx、ZnYang2018、hugtyftg、logan-qiu、psychelzh、Keynman、KeiichiKasai、KawaiiAsh。 +本書は、オープンソースコミュニティの多くの貢献者による共同の努力のもとで、継続的に改善されています。時間と労力を注いでくださったすべての執筆者の皆さんに感謝します。お名前は次のとおりです(GitHub により自動生成された順序):krahets、coderonion、Gonglja、nuomi1、Reanon、justin-tse、hpstory、danielsss、curtishd、night-cruise、S-N-O-R-L-A-X、rongyi、msk397、gvenusleo、khoaxuantu、rivertwilight、K3v123、gyt95、zhuoqinyue、yuelinxin、Zuoxun、mingXta、Phoenix0415、FangYuan33、GN-Yu、longsizhuo、IsChristina、xBLACKICEx、guowei-gong、Cathay-Chen、pengchzn、QiLOL、magentaqin、hello-ikun、JoseHung、qualifier1024、thomasq0、sunshinesDL、L-Super、Guanngxu、Transmigration-zhou、WSL0809、Slone123c、lhxsm、yuan0221、what-is-me、Shyam-Chen、theNefelibatas、longranger2、codeberg-user、xiongsp、JeffersonHuang、prinpal、seven1240、Wonderdch、malone6、xiaomiusa87、gaofer、bluebean-cloud、a16su、SamJin98、hongyun-robot、nanlei、XiaChuerwu、yd-j、iron-irax、mgisr、steventimes、junminhong、heshuyue、danny900714、MolDuM、Nigh、Dr-XYZ、XC-Zero、reeswell、PXG-XPG、NI-SW、Horbin-Magician、Enlightenus、YangXuanyi、beatrix-chan、DullSword、xjr7670、jiaxianhua、qq909244296、iStig、boloboloda、hts0000、gledfish、wenjianmin、keshida、kilikilikid、lclc6、lwbaptx、linyejoe2、liuxjerry、llql1211、fbigm、echo1937、szu17dmy、dshlstarr、Yucao-cy、coderlef、czruby、bongbongbakudan、beintentional、ZongYangL、ZhongYuuu、ZhongGuanbin、hezhizhen、linzeyan、ZJKung、luluxia、xb534、ztkuaikuai、yw-1021、ElaBosak233、baagod、zhouLion、yishangzhang、yi427、yanedie、yabo083、weibk、wangwang105、th1nk3r-ing、tao363、4yDX3906、syd168、sslmj2020、smilelsb、siqyka、selear、sdshaoda、Xi-Row、popozhu、nuquist19、noobcodemaker、XiaoK29、chadyi、lyl625760、lucaswangdev、0130w、shanghai-Jerry、EJackYang、Javesun99、eltociear、lipusheng、KNChiu、BlindTerran、ShiMaRing、lovelock、FreddieLi、FloranceYeh、fanchenggang、gltianwen、goerll、nedchu、curly210102、CuB3y0nd、KraHsu、CarrotDLaw、youshaoXG、bubble9um、Asashishi、Asa0oo0o0o、fanenr、eagleanurag、akshiterate、52coder、foursevenlove、KorsChen、GaochaoZhu、hopkings2008、yang-le、realwujing、Evilrabbit520、Umer-Jahangir、Turing-1024-Lee、Suremotoo、paoxiaomooo、Chieko-Seren、Allen-Scai、ymmmas、Risuntsy、Richard-Zhang1019、RafaelCaso、qingpeng9802、primexiao、Urbaner3、zhongfq、nidhoggfgg、MwumLi、CreatorMetaSky、martinx、ZnYang2018、hugtyftg、logan-qiu、psychelzh、Keynman、KeiichiKasai と KawaiiAsh。 -この本のコードレビュー作業は、coderonion, Gonglja, gvenusleo, hpstory, justin‐tse, khoaxuantu, krahets, night-cruise, nuomi1, Reanon and rongyi(アルファベット順)によって完了されました。彼らの時間と努力に感謝し、様々な言語でのコードの標準化と統一性を確保してくださいました。 +本書のコードレビューは coderonion、curtishd、Gonglja、gvenusleo、hpstory、justin-tse、khoaxuantu、krahets、night-cruise、nuomi1、Reanon と rongyi によって行われました(アルファベット順)。彼らが費やしてくれた時間と労力に感謝します。各言語のコードの規範性と統一性が保たれているのは、まさに彼らのおかげです。 -本書の繁体字中国語版は Shyam-Chen および Dr-XYZ によってレビューされ、英語版は yuelinxin、K3v123、QiLOL、Phoenix0415、SamJin98、yanedie、RafaelCaso、pengchzn、thomasq0、magentaqin によってレビューされ、日本語版は eltociear によってレビューされました。彼らの継続的な貢献のおかげで、本書はより幅広い読者層に提供することができています。ここに深く感謝いたします。 +本書の繁体字中国語版は Shyam-Chen と Dr-XYZ がレビューし、英語版は yuelinxin、K3v123、QiLOL、Phoenix0415、SamJin98、yanedie、RafaelCaso、pengchzn、thomasq0 と magentaqin がレビューし、日本語版は eltociear がレビューしました。彼らの継続的な貢献があってこそ、本書はより幅広い読者に届けられています。感謝いたします。 -この本の制作過程において、多くの方々から貴重な支援をいただきました。これらに限定されませんが: +本書の ePub 電子書籍生成ツールは zhongfq によって開発されました。彼の貢献に感謝します。読者により自由な読書方法を提供してくれました。 -- 会社でのメンター、李熙博士に感謝します。ある会話で「早く行動しろ」と励ましていただき、この本を書く決意を固めることができました。 -- ガールフレンドのBubbleに感謝します。この本の最初の読者として、アルゴリズム初心者の視点から多くの貴重な提案をいただき、この本を初心者により適したものにしてくださいました。 -- Tengbao、Qibao、Feibaoに感謝します。この本のクリエイティブな名前を考えてくださり、みんなが初めて「Hello World!」を書いた時の素晴らしい思い出を呼び起こしてくれました。 -- Xiaoquanに感謝します。知的財産に関する専門的な支援を提供してくださり、このオープンソース本の開発において重要な役割を果たしてくださいました。 -- Sutongに感謝します。この本の美しいカバーとロゴをデザインしてくださり、私の要求で何度も修正を辛抱強く行ってくださいました。 -- @squidfunk に感謝します。執筆と組版の提案、および彼が開発したオープンソースドキュメントテーマ [Material-for-MkDocs](https://github.com/squidfunk/mkdocs-material/tree/master) を提供してくださいました。 +本書の執筆過程で、私は多くの方々の助けを得ました。 -執筆の過程で、データ構造とアルゴリズムに関する多数の教科書や記事を深く研究しました。これらの作品は模範的なモデルとして機能し、この本の内容の正確性と品質を確保してくださいました。先人の方々の貴重な貢献に感謝いたします! +- 会社での私の指導教員である李汐博士に感謝します。ある対話の中で「すぐに行動しよう」と励ましてくださり、この本を書く決意を固めることができました; +- 私の恋人であり、本書の最初の読者でもある泡泡に感謝します。アルゴリズム初心者の視点から多くの貴重な提案をしてくれたおかげで、本書はより初心者に適したものになりました; +- 腾宝、琦宝、飞宝が本書に創造性あふれる名前を付けてくれたことに感謝します。みんなが最初のコード行「Hello World!」を書いた美しい記憶を呼び起こしてくれました; +- 校铨が知的財産の面で専門的な支援をしてくれたことに感謝します。これは本オープンソース書籍の改善に重要な役割を果たしました; +- 苏潼が本書の美しい表紙と logo をデザインし、私の完璧主義につき合って何度も辛抱強く修正してくれたことに感謝します; +- @squidfunk が組版に関する助言を提供してくれたこと、そして彼が開発したオープンソースのドキュメントテーマ [Material-for-MkDocs](https://github.com/squidfunk/mkdocs-material/tree/master) に感謝します。 -この本は、理論と実践を組み合わせた学習を提唱しており、この点で ["Dive into Deep Learning"](https://github.com/d2l-ai/d2l-en) からインスピレーションを受けています。この優れた本をすべての読者に強くお勧めします。 +執筆の過程で、私はデータ構造とアルゴリズムに関する多くの教材や記事を読みました。これらの作品は本書に優れた手本を与え、本書の内容の正確性と品質を支えてくれました。ここに、すべての先生方と先人たちの卓越した貢献に感謝します! -**継続的な支援と励ましにより、この興味深い仕事をすることを可能にしてくださった両親に心から感謝いたします**。 +本書は手と頭を同時に使う学習方法を提唱しています。この点で私は[『手を動かして学ぶ深層学習』](https://github.com/d2l-ai/d2l-zh)から大きな啓発を受けました。ここで読者の皆さんにこの優れた著作を強くお勧めします。 + +**心から両親に感謝します。いつも支え励ましてくれたからこそ、私はこの興味深いことに取り組む機会を得ることができました**。 diff --git a/ja/docs/chapter_preface/index.md b/ja/docs/chapter_preface/index.md index 0a6e890fd..ec8c81329 100644 --- a/ja/docs/chapter_preface/index.md +++ b/ja/docs/chapter_preface/index.md @@ -1,9 +1,9 @@ -# 序文 +# はじめに -![序文](../assets/covers/chapter_preface.jpg) +![はじめに](../assets/covers/chapter_preface.jpg) !!! abstract - アルゴリズムは美しい交響曲のようで、コードの一行一行がリズムのように流れています。 - - この本があなたの心の中で静かに響き、独特で深い旋律を残すことを願っています。 + アルゴリズムは美しい交響曲のようであり、コードの一行一行が旋律のように流れていきます。 + + この本があなたの心の中でそっと響き、独自で深い旋律を残してくれることを願っています。 diff --git a/ja/docs/chapter_preface/suggestions.md b/ja/docs/chapter_preface/suggestions.md index 133902fbf..90607c897 100644 --- a/ja/docs/chapter_preface/suggestions.md +++ b/ja/docs/chapter_preface/suggestions.md @@ -1,25 +1,25 @@ -# 読み方 +# 本書の使い方 !!! tip - 最良の読書体験のために、このセクションを通読することをお勧めします。 + 最適な読書体験を得るために、本節の内容を一通り読むことをおすすめします。 -## 記述規則 +## 文章スタイルの約束 -- タイトルの後に「*」が付いた章は任意であり、比較的難易度の高い内容が含まれています。時間に制約がある場合は、これらをスキップすることをお勧めします。 -- 技術用語は太字(印刷版およびPDF版)または下線(Web版)で表示されます。例えば、配列などです。技術文書をより良く理解するために、これらに慣れることをお勧めします。 -- **太字のテキスト**は重要な内容や要約文を示し、特別な注意を払う価値があります。 -- 特定の意味を持つ単語や句は「引用符」で示され、曖昧さを避けます。 -- プログラミング言語間で一致しない用語については、この本はPythonに従います。例えば、`null`を意味するために`None`を使用します。 -- この本は、よりコンパクトなコンテンツレイアウトと引き換えに、プログラミング言語のコメント規約を部分的に無視しています。コメントは主に3つのタイプで構成されています:タイトルコメント、内容コメント、複数行コメント。 +- 見出しの後に `*` が付いているのは選読章で、内容は比較的難しめです。時間が限られている場合は、先に読み飛ばしてもかまいません。 +- 専門用語は太字(紙書籍版と PDF 版)または下線付き(Web 版)で示します。たとえば配列(array)のようなものです。文献を読む際に役立つため、覚えておくことをおすすめします。 +- 重要な内容やまとめの文は **太字** で示します。これらの文章には特に注意してください。 +- 特定の意味を持つ語句には“引用符”を付け、曖昧さを避けます。 +- プログラミング言語ごとに用語が一致しない場合、本書では Python を基準とします。たとえば、“空”を表すのに `None` を使います。 +- 本書では、よりコンパクトなレイアウトのために、言語ごとのコメント規約を一部省略しています。コメントは主に3種類あります。タイトルコメント、内容コメント、複数行コメントです。 === "Python" ```python title="" - """関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント""" - - # 詳細を説明するためのコメント - + """タイトルコメント。関数、クラス、テストケースなどを示すために使います""" + + # 内容コメント。コードを詳しく説明するために使います + """ 複数行 コメント @@ -29,10 +29,10 @@ === "C++" ```cpp title="" - /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */ - - // 詳細を説明するためのコメント - + /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */ + + // 内容コメント。コードを詳しく説明するために使います + /** * 複数行 * コメント @@ -42,10 +42,10 @@ === "Java" ```java title="" - /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */ - - // 詳細を説明するためのコメント - + /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */ + + // 内容コメント。コードを詳しく説明するために使います + /** * 複数行 * コメント @@ -55,10 +55,10 @@ === "C#" ```csharp title="" - /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */ - - // 詳細を説明するためのコメント - + /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */ + + // 内容コメント。コードを詳しく説明するために使います + /** * 複数行 * コメント @@ -68,10 +68,10 @@ === "Go" ```go title="" - /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */ - - // 詳細を説明するためのコメント - + /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */ + + // 内容コメント。コードを詳しく説明するために使います + /** * 複数行 * コメント @@ -81,10 +81,10 @@ === "Swift" ```swift title="" - /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */ - - // 詳細を説明するためのコメント - + /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */ + + // 内容コメント。コードを詳しく説明するために使います + /** * 複数行 * コメント @@ -94,10 +94,10 @@ === "JS" ```javascript title="" - /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */ - - // 詳細を説明するためのコメント - + /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */ + + // 内容コメント。コードを詳しく説明するために使います + /** * 複数行 * コメント @@ -107,10 +107,10 @@ === "TS" ```typescript title="" - /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */ - - // 詳細を説明するためのコメント - + /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */ + + // 内容コメント。コードを詳しく説明するために使います + /** * 複数行 * コメント @@ -120,10 +120,10 @@ === "Dart" ```dart title="" - /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */ - - // 詳細を説明するためのコメント - + /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */ + + // 内容コメント。コードを詳しく説明するために使います + /** * 複数行 * コメント @@ -133,10 +133,10 @@ === "Rust" ```rust title="" - /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */ - - // 詳細を説明するためのコメント + /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */ + // 内容コメント。コードを詳しく説明するために使います + /** * 複数行 * コメント @@ -146,10 +146,10 @@ === "C" ```c title="" - /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */ - - // 詳細を説明するためのコメント - + /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */ + + // 内容コメント。コードを詳しく説明するために使います + /** * 複数行 * コメント @@ -159,70 +159,83 @@ === "Kotlin" ```kotlin title="" - /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */ - - // 詳細を説明するためのコメント - + /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */ + + // 内容コメント。コードを詳しく説明するために使います + /** * 複数行 * コメント */ ``` -## アニメーション図解による効率的学習 +=== "Ruby" -テキストと比較して、動画や画像は情報密度が高く、より構造化されており、理解しやすくなっています。この本では、**重要で難しい概念は主にアニメーションと図解を通じて提示され**、テキストは説明と補足として機能します。 + ```ruby title="" + ### タイトルコメント。関数、クラス、テストケースなどを示すために使います ### -下図に示すようなアニメーションや図解のある内容に遭遇した場合、**図の理解を優先し、テキストを補足として**、両方を統合して包括的な理解を得てください。 + # 内容コメント。コードを詳しく説明するために使います + + # 複数行 + # コメント + ``` + +## アニメーション図解で効率よく学ぶ + +文字と比べて、動画や画像は情報密度と構造化の度合いが高く、理解しやすいものです。本書では、**重要かつ難解な知識は主にアニメーションによる図解で示し**、文章は説明と補足を担います。 + +本書を読んでいて、ある内容に以下の図のようなアニメーション図解がある場合は、**図を主、文章を従として**、両方を合わせて理解してください。 ![アニメーション図解の例](../index.assets/animation.gif) -## コーディング実践による理解の深化 +## コード実践で理解を深める -この本のソースコードは[GitHubリポジトリ](https://github.com/krahets/hello-algo)でホストされています。下図に示すように、**ソースコードにはテスト例が付属しており、ワンクリックで実行できます**。 +本書のサンプルコードは [GitHub リポジトリ](https://github.com/krahets/hello-algo) で管理されています。以下の図のように、**ソースコードにはテストケースが付いており、ワンクリックで実行できます**。 -時間に余裕がある場合は、**自分でコードをタイプすることをお勧めします**。時間がない場合は、少なくともすべてのコードを読んで実行してください。 +時間に余裕があれば、**コードを見ながら自分で一度書いてみることをおすすめします**。学習時間が限られている場合でも、少なくともすべてのコードに目を通し、実行してください。 -コードを読むだけと比較して、コードを書くことは多くの場合、より多くの学習をもたらします。**実践による学習こそが真の学習方法です。** +コードを読むのに比べて、書く過程のほうが得られるものは多いものです。**手を動かしてこそ、本当に学んだことになります**。 ![コード実行例](../index.assets/running_code.gif) -コードを実行するための設定には、主に3つのステップが含まれます。 +コードを実行する前準備は主に3ステップです。 -**ステップ1:ローカルプログラミング環境をインストール**。付録の[チュートリアル](https://www.hello-algo.com/chapter_appendix/installation/)に従ってインストールするか、すでにインストールされている場合はこのステップをスキップしてください。 +**第1ステップ:ローカルのプログラミング環境をインストールする**。付録の[チュートリアル](https://www.hello-algo.com/chapter_appendix/installation/)を参照してインストールしてください。すでにインストール済みであれば、この手順は省略できます。 -**ステップ2:コードリポジトリをクローンまたはダウンロード**。[GitHubリポジトリ](https://github.com/krahets/hello-algo)を訪問してください。 - -[Git](https://git-scm.com/downloads)がインストールされている場合は、次のコマンドを使用してリポジトリをクローンします: +**第2ステップ:コードリポジトリをクローンまたはダウンロードする**。 [GitHub リポジトリ](https://github.com/krahets/hello-algo) にアクセスしてください。すでに [Git](https://git-scm.com/downloads) をインストールしている場合は、次のコマンドでこのリポジトリをクローンできます: ```shell git clone https://github.com/krahets/hello-algo.git ``` -または、下図に示す場所にある「Download ZIP」ボタンをクリックして、コードを圧縮ZIPファイルとして直接ダウンロードすることもできます。その後、ローカルで展開するだけです。 +もちろん、以下の図に示す場所で“Download ZIP”ボタンをクリックし、コードの圧縮ファイルを直接ダウンロードしてローカルで展開することもできます。 ![リポジトリのクローンとコードのダウンロード](suggestions.assets/download_code.png) -**ステップ3:ソースコードを実行**。下図に示すように、上部にファイル名が記載されたコードブロックについては、リポジトリの`codes`フォルダで対応するソースコードファイルを見つけることができます。これらのファイルはワンクリックで実行でき、不要なデバッグ時間を節約し、学習に集中できます。 +**第3ステップ:ソースコードを実行する**。以下の図のように、上部にファイル名が表示されているコードブロックについては、リポジトリの `codes` フォルダ内に対応するソースコードファイルがあります。ソースコードファイルはワンクリックで実行できるため、不要なデバッグ時間を減らし、学習内容に集中できます。 ![コードブロックと対応するソースコードファイル](suggestions.assets/code_md_to_repo.png) -## 議論による共同学習 +ローカルでコードを実行するだけでなく、**Web 版では Python コードの可視化実行にも対応しています**([pythontutor](https://pythontutor.com/) を利用)。以下の図のように、コードブロックの下にある“可視化実行”をクリックすると表示を展開し、アルゴリズムコードの実行過程を観察できます。また、“全画面表示”をクリックすると、より見やすい閲覧体験が得られます。 -この本を読んでいる間、学べなかった点を飛ばさないでください。**コメントセクションで気軽に質問してください**。喜んでお答えし、通常2日以内に回答できます。 +![Python コードの可視化実行](suggestions.assets/pythontutor_example.png) -下図に示すように、各章の下部にコメントセクションがあります。これらのコメントに注意を払うことをお勧めします。他の人が遭遇した問題を知ることで、知識のギャップを特定し、より深い思索を促すだけでなく、仲間の読者の質問に答えたり、洞察を共有したり、相互の向上を促進したりすることで寛大に貢献することも招待します。 +## 質問と議論を通じてともに成長する -![コメントセクションの例](../index.assets/comment.gif) +本書を読んでいて、理解できていない知識点を安易に読み飛ばさないでください。**コメント欄で気軽に質問してください**。私と仲間たちが誠意をもって回答し、通常は 2 日以内に返信します。 -## アルゴリズム学習パス +以下の図のように、Web 版では各章の下部にコメント欄があります。ぜひコメント欄の内容にも目を通してください。一方では、みんなが直面した問題を知ることで知識の抜けを補い、より深い思考を促せます。もう一方では、ほかの仲間の質問にも積極的に答え、見解を共有し、互いの成長を助けてほしいと思います。 -全体的に、データ構造とアルゴリズムをマスターする旅は3つの段階に分けることができます: +![コメント欄の例](../index.assets/comment.gif) -1. **段階1:アルゴリズムの入門**。さまざまなデータ構造の特性と使用法に慣れ、異なるアルゴリズムの原理、プロセス、用途、効率について学ぶ必要があります。 -2. **段階2:アルゴリズム問題の練習**。[Sword for Offer](https://leetcode.cn/studyplan/coding-interviews/)や[LeetCode Hot 100](https://leetcode.cn/studyplan/top-100-liked/)などの人気のある問題から始めることをお勧めし、少なくとも100問を蓄積して主流のアルゴリズム問題に慣れることです。練習を始めると忘却が課題になる可能性がありますが、これは正常なことですのでご安心ください。「エビングハウスの忘却曲線」に従って問題を復習することができ、通常3〜5回の反復の後、それらを覚えることができるでしょう。 -3. **段階3:知識体系の構築**。学習の面では、アルゴリズムコラム記事、解法フレームワーク、アルゴリズム教科書を読んで知識体系を継続的に豊かにすることができます。練習の面では、トピック別分類、一つの問題に対する複数の解法、複数の問題に対する一つの解法など、高度な戦略を試すことができます。これらの戦略に関する洞察は、さまざまなコミュニティで見つけることができます。 +## アルゴリズム学習ロードマップ -下図に示すように、この本は主に「段階1」をカバーしており、段階2と3により効率的に取り組むのに役立つことを目的としています。 +全体として見ると、データ構造とアルゴリズムの学習過程は 3 つの段階に分けられます。 -![アルゴリズム学習パス](suggestions.assets/learning_route.png) +1. **第 1 段階:アルゴリズム入門**。さまざまなデータ構造の特徴と使い方に慣れ、異なるアルゴリズムの原理、流れ、用途、効率などを学ぶ必要があります。 +2. **第 2 段階:アルゴリズム問題を解く**。まずは人気の高い問題から取り組み、少なくとも 100 問は蓄積して、主流のアルゴリズム問題に慣れることをおすすめします。最初のうちは、“知識の忘却”が課題になるかもしれませんが、心配はいりません。これはごく自然なことです。“エビングハウスの忘却曲線”に沿って問題を復習すれば、通常は 3~5 回繰り返すことでしっかり記憶に定着します。おすすめの問題リストと学習計画は、この [GitHub リポジトリ](https://github.com/krahets/LeetCode-Book) を参照してください。 +3. **第 3 段階:知識体系を構築する**。学習面では、アルゴリズムの連載記事、解法フレームワーク、教材などを読むことで、知識体系を継続的に充実させられます。問題演習の面では、トピック別分類、1 問多解、1 解多題といった発展的な戦略も試せます。関連する学習ノウハウは各コミュニティで見つけられます。 + +以下の図のように、本書の内容は主に“第 1 段階”を扱っており、第 2 段階と第 3 段階の学習をより効率的に進める助けとなることを目的としています。 + +![アルゴリズム学習ロードマップ](suggestions.assets/learning_route.png) diff --git a/ja/docs/chapter_preface/summary.md b/ja/docs/chapter_preface/summary.md index dddf52fab..c97aa42c5 100644 --- a/ja/docs/chapter_preface/summary.md +++ b/ja/docs/chapter_preface/summary.md @@ -1,8 +1,10 @@ # まとめ -- この本の主な読者はアルゴリズムの初心者です。すでに基本的な知識をお持ちの場合、この本はアルゴリズムの知識を体系的に復習するのに役立ち、この本のソースコードは「コーディングツールキット」としても使用できます。 -- この本は3つの主要なセクション、計算量解析、データ構造、アルゴリズムで構成されており、この分野のほとんどのトピックをカバーしています。 -- アルゴリズムの初心者にとって、多くの回り道や一般的な落とし穴を避けるために、初期段階で入門書を読むことが重要です。 -- 本書内のアニメーションと図は通常、重要なポイントと難しい知識を紹介するために使用されます。本を読む際にはこれらにより多くの注意を払う必要があります。 -- 実践はプログラミングを学ぶ最良の方法です。ソースコードを実行し、自分でコードをタイプすることを強くお勧めします。 -- この本のWeb版の各章には議論セクションがあり、いつでも質問や洞察を共有することを歓迎します。 +### 重要ポイントの振り返り + +- 本書の主な対象読者はアルゴリズム初学者です。すでにある程度の基礎がある場合でも、本書はアルゴリズム知識を体系的に振り返る助けとなり、書中のソースコードは「問題演習用ツール集」としても利用できます。 +- 本書の内容は主に計算量解析、データ構造、アルゴリズムの三部からなり、この分野の大部分のテーマを網羅しています。 +- アルゴリズム初心者にとって、学習初期の段階で入門書を読むことは非常に重要であり、多くの遠回りを避けられます。 +- 本書のアニメーション図解は通常、重要な知識や難しい知識を紹介するために用いられます。本書を読む際は、これらの内容により多く注意を払うべきです。 +- 実践はプログラミングを学ぶ最良の方法です。ソースコードを実行し、実際に自分でコードを書くことを強く勧めます。 +- 本書のWeb版の各章にはコメント欄が設けられており、疑問や見解をいつでも共有することを歓迎します。 diff --git a/ja/docs/chapter_reference/index.md b/ja/docs/chapter_reference/index.md index d9db6397a..eca1a8866 100644 --- a/ja/docs/chapter_reference/index.md +++ b/ja/docs/chapter_reference/index.md @@ -10,15 +10,15 @@ icon: material/bookshelf [3] Robert Sedgewick, et al. Algorithms (4th Edition). -[4] Yan Weimin. Data Structures (C Language Version). +[4] 严蔚敏. データ構造(C 言語版). -[5] Deng Junhui. Data Structures (C++ Language Version, Third Edition). +[5] 邓俊辉. データ構造(C++ 言語版、第3版). -[6] Mark Allen Weiss, translated by Chen Yue. Data Structures and Algorithm Analysis in Java (Third Edition). +[6] マーク・アレン・ワイス著,陈越訳. データ構造とアルゴリズム解析:Java言語による記述(第3版). -[7] Cheng Jie. Speaking of Data Structures. +[7] 程杰. データ構造の話. -[8] Wang Zheng. The Beauty of Data Structures and Algorithms. +[8] 王争. データ構造とアルゴリズムの美. [9] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition). diff --git a/ja/docs/chapter_searching/binary_search.md b/ja/docs/chapter_searching/binary_search.md index d62d6d165..8d4d2115d 100644 --- a/ja/docs/chapter_searching/binary_search.md +++ b/ja/docs/chapter_searching/binary_search.md @@ -1,27 +1,27 @@ # 二分探索 -二分探索は分割統治戦略を用いる効率的な探索アルゴリズムです。配列内の要素の整列順序を利用し、各反復で探索区間を半分に減らしながら、目標要素が見つかるか探索区間が空になるまで続行します。 +二分探索(binary search)は分割統治法に基づく効率的な探索アルゴリズムです。データが整列済みである性質を利用し、各ラウンドで探索範囲を半分に縮小し、目標要素を見つけるか探索区間が空になるまで続けます。 !!! question - 長さ$n$の配列`nums`が与えられ、要素は重複なしで昇順に配列されています。この配列内の要素`target`のインデックスを見つけて返してください。配列に要素が含まれていない場合は$-1$を返してください。例を下図に示します。 + 長さ $n$ の配列 `nums` が与えられます。要素は小さい順に並んでおり、重複しません。要素 `target` がこの配列内にある場合はそのインデックスを返し、含まれない場合は $-1$ を返してください。例を次の図に示します。 -![Binary search example data](binary_search.assets/binary_search_example.png) +![二分探索の例](binary_search.assets/binary_search_example.png) -下図に示すように、まず$i = 0$と$j = n - 1$でポインタを初期化し、それぞれ配列の最初と最後の要素を指します。これらはまた全体の探索区間$[0, n - 1]$を表します。角括弧は閉区間を示し、境界値自身も含むことに注意してください。 +次の図に示すように、まずポインタ $i = 0$ と $j = n - 1$ を初期化し、それぞれ配列の先頭要素と末尾要素を指すようにして、探索区間 $[0, n - 1]$ を表します。角括弧は閉区間を表し、境界値自体を含むことに注意してください。 -そして、以下の2つのステップをループで実行する可能性があります。 +次に、以下の 2 つの手順を繰り返します。 -1. 中点インデックス$m = \lfloor {(i + j) / 2} \rfloor$を計算します。ここで$\lfloor \: \rfloor$は床関数を表します。 -2. `nums[m]`と`target`の比較に基づいて、以下の3つのケースのうち1つを選択して実行します。 - 1. `nums[m] < target`の場合、`target`は区間$[m + 1, j]$にあることを示すため、$i = m + 1$とします。 - 2. `nums[m] > target`の場合、`target`は区間$[i, m - 1]$にあることを示すため、$j = m - 1$とします。 - 3. `nums[m] = target`の場合、`target`が見つかったことを示すため、インデックス$m$を返します。 +1. 中央のインデックス $m = \lfloor {(i + j) / 2} \rfloor$ を計算します。ここで $\lfloor \: \rfloor$ は切り捨てを表します。 +2. `nums[m]` と `target` の大小関係を判定し、次の 3 つの場合に分かれます。 + 1. `nums[m] < target` のとき、`target` は区間 $[m + 1, j]$ にあるため、$i = m + 1$ を実行します。 + 2. `nums[m] > target` のとき、`target` は区間 $[i, m - 1]$ にあるため、$j = m - 1$ を実行します。 + 3. `nums[m] = target` のとき、`target` が見つかったので、インデックス $m$ を返します。 -配列に目標要素が含まれていない場合、探索区間は最終的に空になり、$-1$を返して終了します。 +配列に目標要素が含まれない場合、探索区間は最終的に空まで縮小されます。このとき $-1$ を返します。 === "<1>" - ![Binary search process](binary_search.assets/binary_search_step1.png) + ![二分探索の流れ](binary_search.assets/binary_search_step1.png) === "<2>" ![binary_search_step2](binary_search.assets/binary_search_step2.png) @@ -41,43 +41,43 @@ === "<7>" ![binary_search_step7](binary_search.assets/binary_search_step7.png) -$i$と$j$が両方とも`int`型であるため、**$i + j$は`int`型の範囲を超える可能性がある**ことは注目に値します。大きな数のオーバーフローを避けるため、通常は式$m = \lfloor {i + (j - i) / 2} \rfloor$を使用して中点を計算します。 +注意すべき点として、$i$ と $j$ はどちらも `int` 型であるため、**$i + j$ が `int` 型の範囲を超える可能性があります**。大きな数によるオーバーフローを避けるため、通常は式 $m = \lfloor {i + (j - i) / 2} \rfloor$ を用いて中点を計算します。 -コードは以下の通りです: +コードは次のとおりです。 ```src [file]{binary_search}-[class]{}-[func]{binary_search} ``` -**時間計算量は$O(\log n)$です**:二分ループにおいて、区間は各ラウンドで半分に減少するため、反復回数は$\log_2 n$となります。 +**時間計算量は $O(\log n)$** :二分探索のループでは各ラウンドで区間が半分になるため、ループ回数は $\log_2 n$ です。 -**空間計算量は$O(1)$です**:ポインタ$i$と$j$は定数サイズの空間を占有します。 +**空間計算量は $O(1)$** :ポインタ $i$ と $j$ に必要なのは定数サイズの空間だけです。 -## 区間表現方法 +## 区間の表し方 -上記の閉区間の他に、もう一つの一般的な区間表現は「左閉右開」区間で、$[0, n)$として定義され、左境界は自身を含み、右境界は含みません。この表現では、$i = j$のとき区間$[i, j)$は空になります。 +上記の両閉区間のほかに、一般的な区間表現として「左閉右開」区間があり、$[0, n)$ と定義されます。つまり左端は含み、右端は含みません。この表現では、区間 $[i, j)$ は $i = j$ のとき空です。 -この表現に基づいて同じ機能を持つ二分探索アルゴリズムを実装できます: +この表現に基づいて、同じ機能を持つ二分探索アルゴリズムを実装できます。 ```src [file]{binary_search}-[class]{}-[func]{binary_search_lcro} ``` -下図に示すように、2つの区間表現タイプにおいて、二分探索アルゴリズムの初期化、ループ条件、区間縮小操作が異なります。 +次の図に示すように、2 種類の区間表現では、二分探索アルゴリズムの初期化、ループ条件、区間の縮小操作がそれぞれ異なります。 -「閉区間」表現では両方の境界が包含的であるため、ポインタ$i$と$j$による区間縮小操作も対称的です。これによりエラーが発生しにくくなるため、**一般的に「閉区間」アプローチの使用が推奨されます**。 +「両閉区間」の表現では左右の境界がどちらも閉区間として定義されるため、ポインタ $i$ とポインタ $j$ による区間縮小の操作も対称になります。このほうがミスをしにくいため、**一般には「両閉区間」の書き方を推奨します**。 -![Two types of interval definitions](binary_search.assets/binary_search_ranges.png) +![2 種類の区間定義](binary_search.assets/binary_search_ranges.png) -## 利点と制限 +## 利点と限界 -二分探索は時間と空間の両方の面で良好な性能を示します。 +二分探索は時間と空間の両面で優れた性能を持ちます。 -- 二分探索は時間効率が良いです。大きなデータセットでは、対数時間計算量が大きな利点を提供します。例えば、サイズ$n = 2^{20}$のデータセットが与えられた場合、線形探索は$2^{20} = 1048576$回の反復が必要ですが、二分探索は$\log_2 2^{20} = 20$回のループのみで済みます。 -- 二分探索には追加の空間が必要ありません。追加の空間に依存する探索アルゴリズム(ハッシュ探索など)と比較して、二分探索はより空間効率的です。 +- 二分探索は時間効率が高いです。データ量が大きい場合、対数時間計算量は大きな優位性を持ちます。たとえば、データサイズ $n = 2^{20}$ のとき、線形探索では $2^{20} = 1048576$ 回のループが必要ですが、二分探索では $\log_2 2^{20} = 20$ 回で済みます。 +- 二分探索は追加の空間を必要としません。追加領域を要する探索アルゴリズム(たとえばハッシュ探索)と比べて、二分探索はより省メモリです。 -しかし、二分探索は以下の懸念により、すべてのシナリオに適しているとは限りません。 +しかし、二分探索があらゆる状況に適しているわけではなく、主な理由は次のとおりです。 -- 二分探索はソート済みデータにのみ適用できます。未ソートのデータは二分探索を適用する前にソートする必要があり、ソートアルゴリズムは通常$O(n \log n)$の時間計算量を持つため、これは価値がないかもしれません。このコストは線形探索よりも高く、二分探索自体は言うまでもありません。頻繁な挿入があるシナリオでは、配列を順序に保つコストは非常に高く、特定の位置に新しい要素を挿入する時間計算量は$O(n)$です。 -- 二分探索は配列のみを使用できます。二分探索には非連続(ジャンプ)要素アクセスが必要で、これは連結リストでは非効率的です。そのため、連結リストや連結リストに基づくデータ構造はこのアルゴリズムに適していない可能性があります。 -- 線形探索は小さなデータセットでより良い性能を示します。線形探索では各反復で1つの判定操作のみが必要ですが、二分探索では1つの加算、1つの除算、1つから3つの判定操作、1つの加算(減算)を含み、合計4つから6つの操作が必要です。そのため、データサイズ$n$が小さい場合、線形探索は二分探索よりも高速です。 +- 二分探索は整列済みデータにしか適用できません。入力データが無秩序な場合、二分探索を使うためだけにソートするのは割に合いません。ソートアルゴリズムの時間計算量は通常 $O(n \log n)$ であり、線形探索や二分探索よりも高いからです。要素を頻繁に挿入する場面では、配列の整列性を保つために特定位置へ挿入する必要があり、その時間計算量は $O(n)$ と高コストです。 +- 二分探索は配列にしか適していません。二分探索では要素へ飛び飛びにアクセスする必要がありますが、連結リストでそのようなアクセスを行う効率は低いため、連結リストやそれを基に実装されたデータ構造には向きません。 +- データ量が小さい場合は線形探索のほうが高性能です。線形探索では各ラウンドで 1 回の比較だけで済みますが、二分探索では 1 回の加算、1 回の除算、1 ~ 3 回の比較、1 回の加算(減算)が必要で、合計 4 ~ 6 個の基本操作になります。したがって、データ量 $n$ が小さいときは、線形探索のほうがかえって速くなります。 diff --git a/ja/docs/chapter_searching/binary_search_edge.md b/ja/docs/chapter_searching/binary_search_edge.md index adf2ea804..355191b6d 100644 --- a/ja/docs/chapter_searching/binary_search_edge.md +++ b/ja/docs/chapter_searching/binary_search_edge.md @@ -1,39 +1,39 @@ # 二分探索の境界 -## 左境界を見つける +## 左端境界を探す !!! question - 重複要素を含む可能性がある長さ$n$のソート済み配列`nums`が与えられ、最も左の要素`target`のインデックスを返してください。要素が配列に存在しない場合は、$-1$を返してください。 + 長さ $n$ のソート済み配列 `nums` が与えられ、その中には重複要素が含まれる可能性があります。配列内で最も左にある要素 `target` のインデックスを返してください。配列にこの要素が含まれない場合は、$-1$ を返します。 -挿入位置の二分探索方法を思い出すと、探索完了後、インデックス$i$は`target`の最も左の出現を指します。したがって、**挿入位置の探索は本質的に最も左の`target`のインデックスを見つけることと同じです**。 +二分探索で挿入位置を求める方法を思い出すと、探索完了後に $i$ は最も左にある `target` を指します。**したがって、挿入位置を探すことの本質は、最も左にある `target` のインデックスを探すことです**。 -挿入位置を見つける関数を使用して`target`の左境界を見つけることができます。配列に`target`が含まれていない可能性があることに注意してください。これは以下の2つの結果につながる可能性があります: +挿入位置を探す関数を使って左端境界を求めることを考えます。なお、配列に `target` が含まれない場合があり、そのときは次の 2 つの結果が起こりえます。 -- 挿入位置のインデックス$i$が範囲外です。 -- 要素`nums[i]`が`target`と等しくありません。 +- 挿入位置のインデックス $i$ が範囲外になる。 +- 要素 `nums[i]` が `target` と等しくない。 -これらの場合、単に$-1$を返します。コードは以下の通りです: +上の 2 つの状況に当てはまる場合は、直接 $-1$ を返せば十分です。コードは以下のとおりです: ```src [file]{binary_search_edge}-[class]{}-[func]{binary_search_left_edge} ``` -## 右境界を見つける +## 右端境界を探す -`target`の最も右の出現をどのように見つけるでしょうか?最も直接的な方法は、`nums[m] == target`の場合に探索境界を調整する方法を変更して、従来の二分探索ロジックを修正することです。コードはここでは省略されています。興味がある場合は、自分でコードを実装してみてください。 +では、最も右にある `target` はどのように探せるでしょうか。最も直接的な方法はコードを修正し、`nums[m] == target` の場合のポインタの縮小操作を置き換えることです。ここではコードを省略するので、興味があれば自分で実装してみてください。 -以下では、さらに2つの巧妙な方法を紹介します。 +ここでは、より巧妙な 2 つの方法を紹介します。 -### 左境界探索を再利用する +### 左端境界探索を再利用する -`target`の最も右の出現を見つけるには、最も左の`target`を見つけるために使用された関数を再利用できます。具体的には、最も右のターゲットの探索を最も左のターゲット + 1の探索に変換します。 +実際には、最も左の要素を探す関数を利用して最も右の要素を探せます。具体的には、**最も右にある `target` を探すことを、最も左にある `target + 1` を探すことに変換します**。 -下図に示すように、探索完了後、ポインタ$i$は最も左の`target + 1`(存在する場合)を指し、ポインタ$j$は`target`の最も右の出現を指します。したがって、$j$を返すことで右境界が得られます。 +下図のように、探索完了後、ポインタ $i$ は最も左にある `target + 1`(存在する場合)を指し、$j$ は最も右にある `target` を指します。**したがって $j$ を返せばよいです**。 -![Transforming the search for the right boundary into the search for the left boundary](binary_search_edge.assets/binary_search_right_edge_by_left_edge.png) +![右端境界の探索を左端境界の探索に変換する](binary_search_edge.assets/binary_search_right_edge_by_left_edge.png) -返される挿入位置は$i$であることに注意してください。したがって、$j$を得るためには1を引く必要があります: +返される挿入位置は $i$ なので、そこから $1$ を引いて $j$ を得る必要があることに注意してください: ```src [file]{binary_search_edge}-[class]{}-[func]{binary_search_right_edge} @@ -41,16 +41,16 @@ ### 要素探索に変換する -配列に`target`が含まれていない場合、$i$と$j$は最終的に`target`より大きい最初の要素と小さい最初の要素をそれぞれ指します。 +配列に `target` が含まれない場合、最終的に $i$ と $j$ はそれぞれ `target` より大きい最初の要素と、`target` より小さい最初の要素を指すことになります。 -したがって、下図に示すように、配列に存在しない要素を構築して、左と右の境界を探索できます。 +したがって、下図のように、配列中に存在しない要素を構成して、それを使って左右の境界を探せます。 -- 最も左の`target`を見つけるには:`target - 0.5`を探索することに変換でき、ポインタ$i$を返します。 -- 最も右の`target`を見つけるには:`target + 0.5`を探索することに変換でき、ポインタ$j$を返します。 +- 最も左にある `target` の探索:`target - 0.5` を探すことに変換でき、ポインタ $i$ を返します。 +- 最も右にある `target` の探索:`target + 0.5` を探すことに変換でき、ポインタ $j$ を返します。 -![Transforming the search for boundaries into the search for an element](binary_search_edge.assets/binary_search_edge_by_element.png) +![境界の探索を要素の探索に変換する](binary_search_edge.assets/binary_search_edge_by_element.png) -コードはここでは省略されていますが、このアプローチについて注意すべき2つの重要な点があります。 +ここではコードを省略しますが、次の 2 点に注意が必要です。 -- 与えられた配列`nums`には小数が含まれていないため、等しい場合の処理は心配ありません。 -- ただし、このアプローチで小数を導入するには、`target`変数を浮動小数点型に変更する必要があります(Pythonでは変更は不要です)。 +- 与えられた配列には小数が含まれないため、等しい場合をどう処理するかを気にする必要はありません。 +- この方法では小数を導入するため、関数内の変数 `target` を浮動小数点数型に変更する必要があります(Python は変更不要です)。 diff --git a/ja/docs/chapter_searching/binary_search_insertion.md b/ja/docs/chapter_searching/binary_search_insertion.md index 295c845d0..d61940ea3 100644 --- a/ja/docs/chapter_searching/binary_search_insertion.md +++ b/ja/docs/chapter_searching/binary_search_insertion.md @@ -1,26 +1,26 @@ -# 二分探索による挿入 +# 二分探索の挿入位置 -二分探索は目標要素を探索するだけでなく、目標要素の挿入位置を探索するなど、多くの変種問題を解決するためにも使用されます。 +二分探索は目標要素の検索だけでなく、目標要素の挿入位置を探すなど、多くの派生問題の解決にも利用できます。 ## 重複要素がない場合 !!! question - 一意の要素を持つ長さ$n$のソート済み配列`nums`と要素`target`が与えられ、ソート順を維持しながら`target`を`nums`に挿入します。`target`が配列にすでに存在する場合は、既存の要素の左側に挿入します。挿入後の配列における`target`のインデックスを返してください。下図に示す例を参照してください。 + 長さ $n$ の整列済み配列 `nums` と要素 `target` が与えられます。配列には重複要素は存在しません。ここで `target` を配列 `nums` に挿入し、その順序を保ちます。配列中にすでに要素 `target` が存在する場合は、その左側に挿入します。挿入後の配列における `target` のインデックスを返してください。例を以下の図に示します。 -![Example data for binary search insertion point](binary_search_insertion.assets/binary_search_insertion_example.png) +![二分探索の挿入位置の例データ](binary_search_insertion.assets/binary_search_insertion_example.png) -前のセクションの二分探索コードを再利用したい場合、以下の2つの質問に答える必要があります。 +前節の二分探索コードを再利用したい場合は、次の二つの問題に答える必要があります。 -**質問1**:配列にすでに`target`が含まれている場合、挿入位置は既存要素のインデックスになりますか? +**問題 1**:配列に `target` が含まれる場合、挿入位置のインデックスはその要素のインデックスですか? -`target`を等しい要素の左側に挿入するという要件は、新しく挿入される`target`が元の`target`の位置を置き換えることを意味します。つまり、**配列に`target`が含まれている場合、挿入位置は確かにその`target`のインデックスです**。 +問題では `target` を等しい要素の左側に挿入するよう求めているため、新しく挿入された `target` は元の `target` の位置に入ります。つまり、**配列に `target` が含まれる場合、挿入位置のインデックスはその `target` のインデックスです**。 -**質問2**:配列に`target`が含まれていない場合、どのインデックスに挿入されますか? +**問題 2**:配列に `target` が存在しない場合、挿入位置はどの要素のインデックスですか? -二分探索プロセスをさらに考えてみましょう:`nums[m] < target`のとき、ポインタ$i$が移動します。これは、ポインタ$i$が`target`以上の要素に近づいていることを意味します。同様に、ポインタ$j$は常に`target`以下の要素に近づいています。 +二分探索の過程をさらに考えると、`nums[m] < target` のときは $i$ が移動します。これは、ポインタ $i$ が `target` 以上の要素へ近づいていることを意味します。同様に、ポインタ $j$ は常に `target` 以下の要素へ近づいています。 -したがって、二分の終了時には確実に:$i$は`target`より大きい最初の要素を指し、$j$は`target`より小さい最初の要素を指します。**配列に`target`が含まれていない場合、挿入位置は$i$であることは明らかです**。コードは以下の通りです: +したがって二分探索の終了時には、$i$ は最初の `target` より大きい要素を指し、$j$ は最初の `target` より小さい要素を指します。**よって、配列に `target` が含まれない場合、挿入インデックスは $i$ です**。コードは次のとおりです: ```src [file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion_simple} @@ -30,28 +30,28 @@ !!! question - 前の質問に基づいて、配列に重複要素が含まれている可能性があると仮定し、他はすべて同じとします。 + 前問を踏まえ、配列には重複要素が含まれる可能性があるものとし、それ以外の条件は変わりません。 -配列に`target`の複数の出現がある場合、通常の二分探索は`target`の1つの出現のインデックスのみを返すことができ、**その位置の左右に`target`の出現がいくつあるかを特定することはできません**。 +配列中に複数の `target` が存在する場合、通常の二分探索ではそのうち一つの `target` のインデックスしか返せず、**その要素の左側と右側にあといくつ `target` があるかは分かりません**。 -問題では目標要素を最も左の位置に挿入することが要求されているため、**配列内の最も左の`target`のインデックスを見つける必要があります**。最初に下図に示すステップを通してこれを実装することを考えてみましょう。 +問題では目標要素を最も左に挿入する必要があるため、**配列中で最も左にある `target` のインデックスを探す必要があります**。まずは以下の図に示す手順で実現することを考えます。 -1. 二分探索を実行して`target`の任意のインデックス、例えば$k$を見つけます。 -2. インデックス$k$から開始して、最も左の`target`の出現が見つかるまで左に線形探索を行い、このインデックスを返します。 +1. 二分探索を実行し、任意の `target` のインデックスを得て、これを $k$ とします。 +2. インデックス $k$ から始めて左へ線形探索し、最も左の `target` を見つけたら返します。 -![Linear search for the insertion point of duplicate elements](binary_search_insertion.assets/binary_search_insertion_naive.png) +![線形探索による重複要素の挿入位置](binary_search_insertion.assets/binary_search_insertion_naive.png) -この方法は実現可能ですが、線形探索を含むため、時間計算量は$O(n)$です。この方法は、配列に多くの重複する`target`が含まれている場合に非効率です。 +この方法は使用できますが、線形探索を含むため、時間計算量は $O(n)$ です。配列中に重複した `target` が多い場合、この方法の効率は低くなります。 -今度は二分探索コードを拡張することを考えてみましょう。下図に示すように、全体的なプロセスは同じままです。各ラウンドで、まず中間インデックス$m$を計算し、次に`target`と`nums[m]`の値を比較して、以下のケースになります。 +次に、二分探索のコードを拡張することを考えます。以下の図に示すように、全体の流れは変えず、各反復でまず中点インデックス $m$ を計算し、その後 `target` と `nums[m]` の大小関係を判定して、次のいくつかの状況に分けます。 -- `nums[m] < target`または`nums[m] > target`のとき、これは`target`がまだ見つかっていないことを意味するため、通常の二分探索を使用して探索範囲を狭め、**ポインタ$i$と$j$を`target`に近づけます**。 -- `nums[m] == target`のとき、これは`target`より小さい要素が範囲$[i, m - 1]$にあることを示すため、$j = m - 1$を使用して範囲を狭め、**ポインタ$j$を`target`より小さい要素に近づけます**。 +- `nums[m] < target` または `nums[m] > target` のときは、まだ `target` を見つけていないことを意味するため、通常の二分探索と同じ区間縮小を行い、**ポインタ $i$ と $j$ を `target` に近づけます**。 +- `nums[m] == target` のときは、`target` より小さい要素が区間 $[i, m - 1]$ にあることを意味するため、$j = m - 1$ として区間を縮小し、**ポインタ $j$ を `target` より小さい要素に近づけます**。 -ループ後、$i$は最も左の`target`を指し、$j$は`target`より小さい最初の要素を指すため、**インデックス$i$が挿入位置です**。 +ループ終了後、$i$ は最も左の `target` を指し、$j$ は最初の `target` より小さい要素を指すため、**インデックス $i$ が挿入位置です**。 === "<1>" - ![Steps for binary search insertion point of duplicate elements](binary_search_insertion.assets/binary_search_insertion_step1.png) + ![重複要素に対する二分探索の挿入位置の手順](binary_search_insertion.assets/binary_search_insertion_step1.png) === "<2>" ![binary_search_insertion_step2](binary_search_insertion.assets/binary_search_insertion_step2.png) @@ -74,9 +74,9 @@ === "<8>" ![binary_search_insertion_step8](binary_search_insertion.assets/binary_search_insertion_step8.png) -以下のコードを観察してください。分岐`nums[m] > target`と`nums[m] == target`の操作は同じであるため、これら2つの分岐をマージできます。 +以下のコードを観察すると、分岐 `nums[m] > target` と `nums[m] == target` の処理は同じであるため、両者はまとめることができます。 -それでも、ロジックがより明確になり、可読性が向上するため、条件を展開したままにしておくことができます。 +それでも、判定条件を分けたままにしておくことは可能であり、そのほうがロジックがより明確で、可読性も高くなります。 ```src [file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion} @@ -84,8 +84,8 @@ !!! tip - このセクションのコードは「閉区間」を使用しています。「左閉右開」に興味がある場合は、自分でコードを実装してみてください。 + 本節のコードはすべて「両閉区間」の書き方です。興味のある読者は「左閉右開」の書き方を自分で実装してみてください。 -要約すると、二分探索は本質的にポインタ$i$と$j$の探索目標を設定することです。これらの目標は特定の要素(`target`など)または要素の範囲(`target`より小さいものなど)である可能性があります。 +要するに、二分探索とはポインタ $i$ と $j$ にそれぞれ探索目標を設定することにほかなりません。目標は具体的な要素(たとえば `target`)である場合もあれば、要素の範囲(たとえば `target` より小さい要素)である場合もあります。 -二分探索の連続ループにおいて、ポインタ$i$と$j$は段階的に事前定義された目標に近づきます。最終的に、それらは答えを見つけるか、境界を越えた後に停止します。 +繰り返される二分のループの中で、ポインタ $i$ と $j$ はどちらも事前に定めた目標へ徐々に近づいていきます。最終的に、それらは答えを見つけるか、境界を越えたところで停止します。 diff --git a/ja/docs/chapter_searching/index.md b/ja/docs/chapter_searching/index.md index 0dba7ec13..3316c9f48 100644 --- a/ja/docs/chapter_searching/index.md +++ b/ja/docs/chapter_searching/index.md @@ -1,9 +1,9 @@ # 探索 -![Searching](../assets/covers/chapter_searching.jpg) +![探索](../assets/covers/chapter_searching.jpg) !!! abstract - 探索は未知への冒険です。神秘的な空間の隅々まで巡る必要があるかもしれませんし、あるいはすぐに目標を見つけることができるかもしれません。 - - この発見の旅において、それぞれの探査は予期しない答えで終わるかもしれません。 + 探索は未知の冒険であり、私たちは神秘的な空間の隅々まで歩き回る必要があるかもしれず、あるいは素早く目標を特定できるかもしれません。 + + この探索の旅において、すべての探求が思いもよらなかった答えをもたらすかもしれません。 diff --git a/ja/docs/chapter_searching/replace_linear_by_hashing.md b/ja/docs/chapter_searching/replace_linear_by_hashing.md index 863c876ee..d244f163a 100644 --- a/ja/docs/chapter_searching/replace_linear_by_hashing.md +++ b/ja/docs/chapter_searching/replace_linear_by_hashing.md @@ -1,34 +1,34 @@ -# ハッシュ最適化戦略 +# ハッシュによる最適化戦略 -アルゴリズム問題において、**線形探索をハッシュベースの探索に置き換えることで、アルゴリズムの時間計算量を削減することがよくあります**。アルゴリズム問題を使用して理解を深めましょう。 +アルゴリズムの問題では,**線形探索をハッシュ探索に置き換えることでアルゴリズムの時間計算量を下げることがよくあります**。ここでは,あるアルゴリズム問題を通じて理解を深めましょう。 !!! question - 整数配列`nums`と目標要素`target`が与えられ、配列内で「和」が`target`に等しい2つの要素を探索し、それらの配列インデックスを返してください。任意の解が受け入れられます。 + 整数配列 `nums` と目標要素 `target` が与えられたとき,配列内から和が `target` となる 2 つの要素を探索し,それらの配列インデックスを返してください。任意の 1 つの解を返せば十分です。 -## 線形探索:時間を空間と交換 +## 線形探索:時間と引き換えに空間を節約 -すべての可能な組み合わせを直接横断することを考えてみます。下図に示すように、ネストしたループを開始し、各反復で2つの整数の和が`target`に等しいかどうかを判断します。そうであれば、それらのインデックスを返します。 +考えられるすべての組み合わせを直接走査することを考えます。次の図に示すように,2 重ループを開始し,各ラウンドで 2 つの整数の和が `target` であるかを判定します。そうであれば,それらのインデックスを返します。 -![Linear search solution for two-sum problem](replace_linear_by_hashing.assets/two_sum_brute_force.png) +![線形探索で 2 数の和を求める](replace_linear_by_hashing.assets/two_sum_brute_force.png) -コードは以下の通りです: +コードは次のとおりです: ```src [file]{two_sum}-[class]{}-[func]{two_sum_brute_force} ``` -この方法の時間計算量は$O(n^2)$、空間計算量は$O(1)$で、大容量データでは非常に時間がかかる可能性があります。 +この方法の時間計算量は $O(n^2)$ ,空間計算量は $O(1)$ であり,大規模データでは非常に時間がかかります。 -## ハッシュ探索:空間を時間と交換 +## ハッシュ探索:空間と引き換えに時間を節約 -ハッシュテーブルの使用を考えてみましょう。キーと値のペアはそれぞれ配列要素とそのインデックスです。配列をループし、各反復中に下図に示すステップを実行します。 +ハッシュテーブルを利用し,キーと値をそれぞれ配列要素と要素のインデックスにします。配列をループで走査し,各ラウンドで次の図に示す手順を実行します。 -1. 数値`target - nums[i]`がハッシュテーブルにあるかどうかを確認します。ある場合は、これら2つの要素のインデックスを直接返します。 -2. キーと値のペア`nums[i]`とインデックス`i`をハッシュテーブルに追加します。 +1. 数値 `target - nums[i]` がハッシュテーブル内にあるかを判定します。あれば,この 2 つの要素のインデックスを直接返します。 +2. キーと値の組 `nums[i]` とインデックス `i` をハッシュテーブルに追加します。 === "<1>" - ![Help hash table solve two-sum](replace_linear_by_hashing.assets/two_sum_hashtable_step1.png) + ![補助ハッシュテーブルで 2 数の和を求める](replace_linear_by_hashing.assets/two_sum_hashtable_step1.png) === "<2>" ![two_sum_hashtable_step2](replace_linear_by_hashing.assets/two_sum_hashtable_step2.png) @@ -36,12 +36,12 @@ === "<3>" ![two_sum_hashtable_step3](replace_linear_by_hashing.assets/two_sum_hashtable_step3.png) -実装コードは以下に示され、単一のループのみが必要です: +実装コードは次のとおりで,単一ループだけで済みます: ```src [file]{two_sum}-[class]{}-[func]{two_sum_hash_table} ``` -この方法は、ハッシュ探索を使用することで時間計算量を$O(n^2)$から$O(n)$に削減し、実行時効率を大幅に向上させます。 +この方法ではハッシュ探索によって時間計算量を $O(n^2)$ から $O(n)$ に下げ,実行効率を大幅に向上させます。 -追加のハッシュテーブルを維持する必要があるため、空間計算量は$O(n)$です。**それにもかかわらず、この方法は全体的により均衡のとれた時空間効率を持ち、この問題の最適解となります**。 +追加のハッシュテーブルを維持する必要があるため,空間計算量は $O(n)$ です。**それでも,この方法は全体として時間と空間の効率のバランスがより良く,本問の最適解です**。 diff --git a/ja/docs/chapter_searching/searching_algorithm_revisited.md b/ja/docs/chapter_searching/searching_algorithm_revisited.md index 49b8c79c7..2fd4c8f87 100644 --- a/ja/docs/chapter_searching/searching_algorithm_revisited.md +++ b/ja/docs/chapter_searching/searching_algorithm_revisited.md @@ -1,84 +1,84 @@ -# 探索アルゴリズムの再検討 +# 探索アルゴリズム再考 -探索アルゴリズム(検索アルゴリズム)は、配列、連結リスト、木、グラフなどのデータ構造内で特定の基準を満たす1つ以上の要素を取得するために使用されます。 +探索アルゴリズム(searching algorithm)は、データ構造(配列、連結リスト、木、グラフなど)の中から、特定の条件を満たす 1 つまたは複数の要素を探索するために用いられます。 -探索アルゴリズムは、そのアプローチに基づいて以下の2つのカテゴリに分けることができます。 +探索アルゴリズムは、実装の考え方に応じて次の 2 種類に分けられます。 -- **データ構造を横断することで目標要素を特定する**:配列、連結リスト、木、グラフの横断など。 -- **データの組織構造や既存のデータを使用して効率的な要素探索を実現する**:二分探索、ハッシュ探索、二分探索木探索など。 +- **データ構造を走査して目標要素を特定する方法**。配列、連結リスト、木、グラフの走査などがこれに当たります。 +- **データの構成やデータに含まれる事前情報を利用して、要素を効率よく探す方法**。二分探索、ハッシュ探索、二分探索木による探索などがこれに当たります。 -これらのトピックは前の章で紹介されたため、私たちには馴染みのないものではありません。このセクションでは、より体系的な観点から探索アルゴリズムを再検討します。 +これらのトピックはすでに前の章で扱っているため、探索アルゴリズムは私たちにとって見慣れたものです。本節では、より体系的な視点から探索アルゴリズムをあらためて見直します。 ## 総当たり探索 -総当たり探索は、データ構造のすべての要素を横断することで目標要素を特定します。 +総当たり探索は、データ構造の各要素を順に調べて目標要素を特定します。 -- 「線形探索」は配列や連結リストなどの線形データ構造に適しています。データ構造の一端から開始し、目標要素が見つかるか、目標要素を見つけることなく他端に到達するまで、各要素に一つずつアクセスします。 -- 「幅優先探索」と「深さ優先探索」は、グラフと木の2つの横断戦略です。幅優先探索は初期ノードから開始し、層ごと(左から右へ)に探索し、近くから遠くのノードにアクセスします。深さ優先探索は初期ノードから開始し、パスの終端(上から下へ)まで追跡し、その後バックトラックして他のパスを試し、データ構造全体が横断されるまで続行します。 +- “線形探索”は配列や連結リストなどの線形データ構造に適しています。データ構造の一端から始めて、要素を 1 つずつ調べ、目標要素が見つかるか、もう一方の端に達しても見つからないまで続けます。 +- “幅優先探索”と“深さ優先探索”は、グラフと木における 2 つの走査戦略です。幅優先探索は初期ノードから始めて層ごとに探索し、近いところから遠いところへ各ノードを訪れます。深さ優先探索は初期ノードから始めて 1 本の経路を最後までたどり、その後でバックトラックしてほかの経路を試し、データ構造全体を走査し終えるまで続けます。 -総当たり探索の利点は、その単純さと汎用性であり、**データの前処理や追加のデータ構造の助けが不要**です。 +総当たり探索の利点は、単純で汎用性が高く、**データの前処理や追加のデータ構造を必要としない**ことです。 -ただし、**このタイプのアルゴリズムの時間計算量は$O(n)$**で、$n$は要素数であるため、大規模なデータセットでは性能が悪くなります。 +しかし、**この種のアルゴリズムの時間計算量は $O(n)$ です**。ここで $n$ は要素数であり、そのためデータ量が大きい場合は性能が低くなります。 -## 適応的探索 +## 適応的な探索 -適応的探索は、データの固有の性質(順序など)を使用して探索プロセスを最適化し、それにより目標要素をより効率的に特定します。 +適応的な探索は、データが持つ固有の性質(整列性など)を利用して探索過程を最適化し、目標要素をより効率よく特定します。 -- 「二分探索」はデータの整列性を使用して効率的な探索を実現し、配列にのみ適用可能です。 -- 「ハッシュ探索」はハッシュテーブルを使用して探索データと目標データの間にキーと値のマッピングを確立し、それによりクエリ操作を実装します。 -- 特定の木構造(二分探索木など)での「木探索」は、ノード値の比較に基づいてノードを迅速に除外し、それにより目標要素を特定します。 +- “二分探索”は、データの順序性を利用して効率的な探索を行う方法で、配列にしか適用できません。 +- “ハッシュ探索”は、ハッシュ表を用いて探索対象のデータと目標データをキーと値の対応にし、問い合わせ操作を実現します。 +- “木探索”は、特定の木構造(たとえば二分探索木)の中で、ノード値の比較に基づいて不要なノードをすばやく除外し、目標要素を特定します。 -これらのアルゴリズムの利点は高効率であり、**時間計算量が$O(\log n)$または$O(1)$にまで達します**。 +この種のアルゴリズムの利点は効率が高く、**時間計算量が $O(\log n)$ あるいは $O(1)$ に達する**ことです。 -ただし、**これらのアルゴリズムを使用するには、多くの場合データの前処理が必要です**。例えば、二分探索では事前に配列をソートする必要があり、ハッシュ探索と木探索の両方で追加のデータ構造の助けが必要です。これらの構造を維持することも、時間と空間の面でより多くのオーバーヘッドが必要です。 +しかし、**これらのアルゴリズムを使うには、たいていデータの前処理が必要です**。たとえば、二分探索では事前に配列をソートする必要があり、ハッシュ探索と木探索では追加のデータ構造が必要です。これらのデータ構造を維持するにも、追加の時間と空間のコストがかかります。 !!! tip - 適応的探索アルゴリズムは、多くの場合探索アルゴリズムと呼ばれ、**主に特定のデータ構造内で目標要素を迅速に取得するために使用されます**。 + 適応的な探索アルゴリズムは、しばしば検索アルゴリズムとも呼ばれ、**主に特定のデータ構造の中で目標要素を高速に取得するために用いられます**。 -## 探索方法の選択 +## 探索手法の選択 -サイズ$n$のデータセットが与えられた場合、線形探索、二分探索、木探索、ハッシュ探索、またはその他の方法を使用して目標要素を取得できます。これらの方法の動作原理を下図に示します。 +大きさ $n$ のデータ集合が与えられたとき、線形探索、二分探索、木探索、ハッシュ探索など、さまざまな方法で目標要素を探索できます。各手法の動作原理を下図に示します。 -![Various search strategies](searching_algorithm_revisited.assets/searching_algorithms.png) +![複数の探索戦略](searching_algorithm_revisited.assets/searching_algorithms.png) -前述の方法の特性と操作効率を以下の表に示します。 +上記のいくつかの手法について、操作効率と特性を次の表に示します。 -

  探索アルゴリズム効率の比較

+

  探索アルゴリズムの効率比較

-| | 線形探索 | 二分探索 | 木探索 | ハッシュ探索 | -| ------------------ | ------------- | --------------------- | --------------------------- | -------------------------- | -| 要素探索 | $O(n)$ | $O(\log n)$ | $O(\log n)$ | $O(1)$ | -| 要素挿入 | $O(1)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | -| 要素削除 | $O(n)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | -| 追加空間 | $O(1)$ | $O(1)$ | $O(n)$ | $O(n)$ | -| データ前処理 | / | ソート $O(n \log n)$ | 木構築 $O(n \log n)$ | ハッシュテーブル構築 $O(n)$ | -| データ順序性 | 無順序 | 順序 | 順序 | 無順序 | +| | 線形探索 | 二分探索 | 木探索 | ハッシュ探索 | +| ------------ | -------- | ------------------ | ------------------ | --------------- | +| 要素探索 | $O(n)$ | $O(\log n)$ | $O(\log n)$ | $O(1)$ | +| 要素挿入 | $O(1)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | +| 要素削除 | $O(n)$ | $O(n)$ | $O(\log n)$ | $O(1)$ | +| 追加領域 | $O(1)$ | $O(1)$ | $O(n)$ | $O(n)$ | +| データ前処理 | / | ソート $O(n \log n)$ | 木構築 $O(n \log n)$ | ハッシュ表構築 $O(n)$ | +| データの順序性 | なし | あり | あり | なし | -探索アルゴリズムの選択は、データ量、探索性能要件、データクエリと更新の頻度などにも依存します。 +探索アルゴリズムの選択は、規模、探索性能の要求、データの問い合わせ頻度や更新頻度などにも左右されます。 **線形探索** -- 汎用性が良く、データ前処理操作が不要です。データを一度だけクエリする必要がある場合、他の3つの方法のデータ前処理時間は線形探索の時間よりも長くなります。 -- 小容量のデータに適しており、時間計算量が効率に与える影響は小さいです。 -- データ更新が非常に頻繁なシナリオに適しています。この方法はデータの追加メンテナンスを必要としないためです。 +- 汎用性が高く、データの前処理をまったく必要としません。データを 1 回だけ問い合わせればよい場合、ほか 3 つの手法では前処理にかかる時間のほうが、線形探索そのものより長くなることがあります。 +- 規模の小さいデータに適しています。この場合、時間計算量が効率に与える影響は比較的小さいです。 +- データ更新頻度が高い場面に適しています。この手法では、データに対する追加の保守が不要だからです。 **二分探索** -- より大きなデータ量に適しており、安定した性能と最悪ケースの時間計算量$O(\log n)$を持ちます。 -- ただし、データ量が大きすぎることはできません。配列の保存には連続したメモリ空間が必要だからです。 -- 頻繁な追加と削除があるシナリオには適していません。順序付き配列の維持に多くのオーバーヘッドが発生するためです。 +- 大規模データに適しており、効率が安定しています。最悪時間計算量は $O(\log n)$ です。 +- データ量が大きすぎる場合には向きません。配列の格納には連続したメモリ領域が必要だからです。 +- 頻繁な挿入・削除がある場面には向きません。整列配列を維持するコストが高いためです。 **ハッシュ探索** -- 高速クエリ性能が不可欠なシナリオに適しており、平均時間計算量は$O(1)$です。 -- 順序付きデータや範囲探索が必要なシナリオには適していません。ハッシュテーブルはデータの順序性を維持できないためです。 +- 問い合わせ性能への要求が高い場面に適しており、平均時間計算量は $O(1)$ です。 +- 順序付きデータや範囲探索が必要な場面には向きません。ハッシュ表ではデータの順序性を維持できないからです。 - ハッシュ関数とハッシュ衝突処理戦略への依存度が高く、性能劣化のリスクが大きいです。 -- 過度に大容量のデータには適していません。ハッシュテーブルは衝突を最小化し、良好なクエリ性能を提供するために追加の空間が必要だからです。 +- データ量が大きすぎる場合には向きません。ハッシュ表は衝突をできるだけ減らして良好な問い合わせ性能を出すために、追加の空間を必要とするからです。 **木探索** -- 大容量データに適しています。木ノードはメモリ内に分散して保存されるためです。 -- 順序付きデータの維持や範囲探索に適しています。 -- ノードの継続的な追加と削除により、二分探索木は偏る可能性があり、時間計算量が$O(n)$に劣化する可能性があります。 -- AVL木や赤黒木を使用する場合、操作は$O(\log n)$効率で安定して実行できますが、木のバランスを維持する操作により追加のオーバーヘッドが追加されます。 +- 巨大データに適しています。木ノードはメモリ上に分散して格納されるためです。 +- 順序付きデータの維持や範囲探索が必要な場面に適しています。 +- ノードの挿入・削除を続ける過程で、二分探索木は偏ることがあり、時間計算量は $O(n)$ まで劣化する可能性があります。 +- AVL 木や赤黒木を使えば、各種操作を $O(\log n)$ の効率で安定して実行できますが、木の平衡を保つ処理による追加コストが発生します。 diff --git a/ja/docs/chapter_searching/summary.md b/ja/docs/chapter_searching/summary.md index a5002b727..d51901199 100644 --- a/ja/docs/chapter_searching/summary.md +++ b/ja/docs/chapter_searching/summary.md @@ -1,8 +1,10 @@ # まとめ -- 二分探索はデータの順序に依存し、探索区間を反復的に半分にすることで探索を実行します。入力データがソート済みである必要があり、配列または配列ベースのデータ構造にのみ適用可能です。 -- 無順序データセット内のエントリを見つけるには、総当たり探索が必要な場合があります。データ構造に基づいて異なる探索アルゴリズムを適用できます:線形探索は配列と連結リストに適しており、幅優先探索(BFS)と深さ優先探索(DFS)はグラフと木に適しています。これらのアルゴリズムは非常に汎用性が高く、データの前処理が不要ですが、$O(n)$という高い時間計算量を持ちます。 -- ハッシュ探索、木探索、二分探索は効率的な探索方法で、特定のデータ構造内で目標要素を迅速に特定できます。これらのアルゴリズムは非常に効率的で、時間計算量が$O(\log n)$または$O(1)$にまで達しますが、通常は追加のデータ構造を収容するために追加の空間が必要です。 -- 実際には、データ量、探索性能要件、データクエリと更新頻度などの要因を分析して、適切な探索方法を選択する必要があります。 -- 線形探索は小さなデータや頻繁に更新される(変動性の高い)データに理想的です。二分探索は大きくてソート済みのデータに適しています。ハッシュ探索は高いクエリ効率が必要で範囲クエリが不要なデータに適しています。木探索は順序を維持し、範囲クエリをサポートする必要がある大きな動的データに最も適しています。 -- 線形探索をハッシュ探索に置き換えることは、実行時性能を最適化する一般的な戦略で、時間計算量を$O(n)$から$O(1)$に削減します。 +### 要点の振り返り + +- 二分探索はデータの順序性に依存し、ループによって探索区間を半分ずつ縮小しながら探索を行う。入力データがソート済みであることを前提とし、配列または配列ベースで実装されたデータ構造にのみ適用できる。 +- 総当たり探索はデータ構造を走査してデータを特定する。線形探索は配列と連結リストに適しており、幅優先探索と深さ優先探索はグラフと木に適している。この種のアルゴリズムは汎用性が高く、データの前処理を必要としないが、時間計算量 $O(n)$ は高い。 +- ハッシュ探索、木探索、二分探索は高効率な探索手法であり、特定のデータ構造内で目的の要素を高速に特定できる。この種のアルゴリズムは効率が高く、時間計算量は $O(\log n)$ あるいは $O(1)$ に達するが、通常は追加のデータ構造を必要とする。 +- 実際には、データ規模、探索性能の要件、データの問い合わせ頻度や更新頻度などの要因を具体的に分析し、そのうえで適切な探索手法を選択する必要がある。 +- 線形探索は小規模または頻繁に更新されるデータに適している。二分探索は大規模でソート済みのデータに適している。ハッシュ探索は問い合わせ効率への要求が高く、範囲検索を必要としないデータに適している。木探索は順序の維持と範囲検索のサポートが必要な大規模動的データに適している。 +- ハッシュ探索で線形探索を置き換えることは、実行時間を最適化するための一般的な戦略であり、時間計算量を $O(n)$ から $O(1)$ へと下げられる。 diff --git a/ja/docs/chapter_sorting/bubble_sort.md b/ja/docs/chapter_sorting/bubble_sort.md index 33ec400be..daef9075d 100644 --- a/ja/docs/chapter_sorting/bubble_sort.md +++ b/ja/docs/chapter_sorting/bubble_sort.md @@ -1,11 +1,11 @@ # バブルソート -バブルソートは、隣接する要素を継続的に比較し交換することで動作します。このプロセスは泡が底から上に上昇するようなものなので、「バブルソート」と名付けられました。 +バブルソート(bubble sort)は、隣接する要素を繰り返し比較して交換することで整列を行います。この過程が泡のように下から上へ浮かび上がることから、バブルソートと呼ばれます。 -下図に示すように、バブリングプロセスは要素交換を使用してシミュレートできます:配列の左端から開始して右に移動し、隣接する要素の各ペアを比較します。左の要素が右の要素より大きい場合は、それらを交換します。横断後、最大要素は配列の右端にバブルアップします。 +次の図に示すように、バブル処理は要素の交換操作によってシミュレートできます。配列の最も左の端から右へ走査し、隣接する要素の大小を順に比較して、「左要素 > 右要素」であれば両者を交換します。走査が終わると、最大の要素は配列の最も右端へ移動します。 === "<1>" - ![Simulating bubble process using element swap](bubble_sort.assets/bubble_operation_step1.png) + ![要素の交換操作でバブル処理をシミュレート](bubble_sort.assets/bubble_operation_step1.png) === "<2>" ![bubble_operation_step2](bubble_sort.assets/bubble_operation_step2.png) @@ -25,18 +25,18 @@ === "<7>" ![bubble_operation_step7](bubble_sort.assets/bubble_operation_step7.png) -## アルゴリズムプロセス +## アルゴリズムの流れ -配列の長さを$n$とします。バブルソートのステップは下図に示されます: +配列の長さを $n$ とすると、バブルソートの手順は次の図のとおりです。 -1. まず、$n$個の要素に対して1回の「バブル」パスを実行し、**最大要素を正しい位置に交換します**。 -2. 次に、残りの$n - 1$個の要素に対して「バブル」パスを実行し、**2番目に大きい要素を正しい位置に交換します**。 -3. この方法で続行します;$n - 1$回のパスの後、**最大$n - 1$個の要素が正しい位置に移動されます**。 -4. 残りの唯一の要素は**必ず**最小であるため、**さらなる**ソートは必要ありません。この時点で、配列はソートされます。 +1. まず、$n$ 個の要素に対して「バブル処理」を行い、**配列中の最大要素を正しい位置へ交換します**。 +2. 次に、残りの $n - 1$ 個の要素に対して「バブル処理」を行い、**2 番目に大きい要素を正しい位置へ交換します**。 +3. このようにして、$n - 1$ 回の「バブル処理」を終えると、**大きいほうから $n - 1$ 個の要素がすべて正しい位置へ交換されます**。 +4. 残った 1 つの要素は必ず最小要素なので、並べ替える必要はなく、これで配列のソートが完了します。 -![Bubble sort process](bubble_sort.assets/bubble_sort_overview.png) +![バブルソートの流れ](bubble_sort.assets/bubble_sort_overview.png) -コード例は以下の通りです: +コード例は次のとおりです。 ```src [file]{bubble_sort}-[class]{}-[func]{bubble_sort} @@ -44,16 +44,16 @@ ## 効率の最適化 -「バブリング」のラウンド中に交換が発生しない場合、配列はすでにソートされているため、すぐに戻ることができます。これを検出するために、`flag`変数を追加できます;パスで交換が行われない場合は、フラグを設定して早期に戻ります。 +ある回の「バブル処理」で交換操作が一度も行われなければ、配列はすでにソート済みであり、結果をそのまま返せることがわかります。したがって、この状況を検出するためのフラグ `flag` を追加し、発生した時点で直ちに返すようにできます。 -この最適化があっても、バブルソートの最悪時間計算量と平均時間計算量は$O(n^2)$のままです。ただし、入力配列がすでにソートされている場合、最良ケース時間計算量は$O(n)$まで低くなる可能性があります。 +最適化後も、バブルソートの最悪時間計算量と平均時間計算量は依然として $O(n^2)$ です。ただし、入力配列が完全に整列済みであれば、最良時間計算量は $O(n)$ に達します。 ```src [file]{bubble_sort}-[class]{}-[func]{bubble_sort_with_flag} ``` -## アルゴリズムの特性 +## アルゴリズムの特徴 -- **$O(n^2)$の時間計算量、適応ソート。** 各「バブリング」ラウンドは長さ$n - 1$、$n - 2$、$\dots$、$2$、$1$の配列セグメントを横断し、合計は$(n - 1) n / 2$となります。`flag`最適化により、配列がすでにソートされている場合、最良ケース時間計算量は$O(n)$に達する可能性があります。 -- **$O(1)$の空間計算量、インプレースソート。** ポインタ$i$と$j$によって定数量の追加空間のみが使用されます。 -- **安定ソート。** 等しい要素は「バブリング」中に交換されないため、元の順序が保持され、これは安定ソートになります。 +- **時間計算量は $O(n^2)$、適応的ソート**:各回の「バブル処理」で走査する配列の長さは順に $n - 1$、$n - 2$、$\dots$、$2$、$1$ であり、その総和は $(n - 1) n / 2$ です。`flag` による最適化を導入すると、最良時間計算量は $O(n)$ に達します。 +- **空間計算量は $O(1)$、インプレースソート**:ポインタ $i$ と $j$ は定数サイズの追加領域しか使用しません。 +- **安定ソート**:「バブル処理」では等しい要素に出会っても交換しないためです。 diff --git a/ja/docs/chapter_sorting/bucket_sort.md b/ja/docs/chapter_sorting/bucket_sort.md index c143f301f..f2be5438f 100644 --- a/ja/docs/chapter_sorting/bucket_sort.md +++ b/ja/docs/chapter_sorting/bucket_sort.md @@ -1,45 +1,45 @@ # バケットソート -前述のソートアルゴリズムはすべて「比較ベースのソートアルゴリズム」で、値を比較することで要素をソートします。このようなソートアルゴリズムは $O(n \log n)$ より良い時間計算量を持つことはできません。次に、線形時間計算量を達成できるいくつかの「非比較ソートアルゴリズム」について議論します。 +前述のいくつかのソートアルゴリズムは、いずれも「比較ベースのソートアルゴリズム」に属し、要素間の大小を比較することで整列を実現します。この種のソートアルゴリズムの時間計算量は $O(n \log n)$ を超えられません。続いて、時間計算量が線形オーダーに達しうる「非比較ソートアルゴリズム」をいくつか見ていきます。 -バケットソートは分割統治戦略の典型的な応用です。一連の順序付けられたバケットを設定し、各バケットがデータの範囲を含み、入力データをこれらのバケットに均等に分散させることで動作します。そして、各バケット内のデータを個別にソートします。最後に、すべてのバケットからのソート済みデータを順次マージして最終結果を生成します。 +バケットソート(bucket sort)は分割統治戦略の典型的な応用です。大小関係をもつ複数のバケットを用意し、各バケットがあるデータ範囲に対応するようにして、データを各バケットへ均等に分配します。その後、各バケット内でそれぞれソートを行い、最後にバケットの順序に従ってすべてのデータを結合します。 -## アルゴリズムの過程 +## アルゴリズムの流れ -長さ $n$ の配列で、$[0, 1)$ の範囲の浮動小数点数を考えてみます。バケットソートの過程は以下の図に示されています。 +長さ $n$ の配列を考え、その要素は範囲 $[0, 1)$ の浮動小数点数であるとします。バケットソートの流れを以下の図に示します。 -1. $k$ 個のバケットを初期化し、$n$ 個の要素をこれらの $k$ 個のバケットに分散させます。 -2. 各バケットを個別にソートします(プログラミング言語の組み込みソート関数を使用)。 -3. 最小から最大のバケットの順序で結果をマージします。 +1. $k$ 個のバケットを初期化し、$n$ 個の要素を $k$ 個のバケットに分配します。 +2. 各バケットに対してそれぞれソートを実行します(ここではプログラミング言語の組み込みソート関数を用います)。 +3. バケットを小さい順にたどって結果を結合します。 -![バケットソートアルゴリズムの過程](bucket_sort.assets/bucket_sort_overview.png) +![バケットソートの流れ](bucket_sort.assets/bucket_sort_overview.png) -コードは以下の通りです: +コードは以下のとおりです: ```src [file]{bucket_sort}-[class]{}-[func]{bucket_sort} ``` -## アルゴリズムの特徴 +## アルゴリズムの特性 -バケットソートは非常に大きなデータセットの処理に適しています。例えば、入力データに100万個の要素が含まれ、システムメモリの制限によりすべてのデータを同時にロードできない場合、データを1,000個のバケットに分割し、各バケットを個別にソートしてから結果をマージできます。 +バケットソートは、非常に大規模なデータの処理に適しています。たとえば、入力データに 100 万個の要素が含まれ、空間の制約によりシステムメモリへすべてのデータを一度に読み込めない場合です。このとき、データを 1000 個のバケットに分け、それぞれのバケットを個別にソートしてから、最後に結果を結合できます。 -- **時間計算量は $O(n + k)$**:要素がバケット間で均等に分散されていると仮定すると、各バケット内の要素数は $n/k$ です。単一のバケットのソートに $O(n/k \log(n/k))$ 時間がかかると仮定すると、すべてのバケットのソートに $O(n \log(n/k))$ 時間がかかります。**バケット数 $k$ が比較的大きいとき、時間計算量は $O(n)$ に近づきます**。結果のマージには、すべてのバケットと要素を走査する必要があり、$O(n + k)$ 時間がかかります。最悪の場合、すべてのデータが単一のバケットに分散され、そのバケットのソートには $O(n^2)$ 時間がかかります。 -- **空間計算量は $O(n + k)$、非インプレースソート**:$k$ 個のバケットと合計 $n$ 個の要素のための追加スペースが必要です。 -- バケットソートが安定かどうかは、各バケット内で使用されるソートアルゴリズムが安定かどうかに依存します。 +- **時間計算量は $O(n + k)$** :要素が各バケット内に平均的に分布していると仮定すると、各バケット内の要素数は $\frac{n}{k}$ です。1 つのバケットをソートするのに $O(\frac{n}{k} \log\frac{n}{k})$ の時間がかかるなら、すべてのバケットのソートには $O(n \log\frac{n}{k})$ の時間がかかります。**バケット数 $k$ が十分大きいとき、時間計算量は $O(n)$ に近づきます**。結果を結合する際には、すべてのバケットと要素を走査する必要があり、$O(n + k)$ の時間を要します。最悪の場合、すべてのデータが 1 つのバケットに割り当てられ、そのバケットのソートに $O(n^2)$ の時間がかかります。 +- **空間計算量は $O(n + k)$、非インプレースソート**:$k$ 個のバケットと合計 $n$ 個の要素ぶんの追加領域が必要です。 +- バケットソートが安定かどうかは、バケット内要素のソートに用いるアルゴリズムが安定かどうかに依存します。 -## 均等分散を達成する方法 +## 均等な分配を実現するには -バケットソートの理論的時間計算量は $O(n)$ に達することができます。**重要なことは、すべてのバケットに要素を均等に分散させることです**。実世界のデータはしばしば均一に分散されていないからです。例えば、eBayのすべての商品を価格範囲で10個のバケットに均等に分散させたいとします。しかし、商品価格の分散は均等でない可能性があり、100ドル未満の商品が多く、500ドル以上の商品が少ないかもしれません。価格範囲を均等に10分割すると、各バケットの商品数の差が大きくなります。 +バケットソートの時間計算量は理論上 $O(n)$ に達しますが、**鍵は要素を各バケットへ均等に分配すること** にあります。実際のデータは均一に分布していないことが多いからです。たとえば、Taobao 上のすべての商品を価格帯ごとに 10 個のバケットへ均等に分けたいとしても、商品の価格分布は偏っており、100 元未満は非常に多く、1000 元超は非常に少ないかもしれません。価格区間を単純に 10 等分すると、各バケットの商品数には大きな差が生じます。 -均等分散を達成するために、最初におおよその境界を設定して、データを3つのバケットに大まかに分割できます。**分散が完了した後、より多くのアイテムを持つバケットをさらに3つのバケットに分割し、すべてのバケットの要素数がほぼ等しくなるまで続けます**。 +均等な分配を実現するために、まず大まかな境界線を設定し、データをひとまず 3 個のバケットに粗く振り分けます。**分配後は、商品数の多いバケットをさらに 3 個のバケットに分割し、すべてのバケット内の要素数がおおむね等しくなるまでこれを続けます**。 -以下の図に示すように、この方法は本質的に再帰木を構築し、葉ノードの要素数ができるだけ均等になることを目指します。もちろん、各ラウンドでデータを3つのバケットに分割する必要はありません - 分割戦略はデータの独特な特性に適応的に調整できます。 +以下の図に示すように、この方法の本質は再帰木を構築することにあり、目標は葉ノードの値をできるだけ均等にすることです。もちろん、毎回データを 3 個のバケットに分割する必要はなく、具体的な分け方はデータの特徴に応じて柔軟に選べます。 -![バケットの再帰的分割](bucket_sort.assets/scatter_in_buckets_recursively.png) +![再帰的にバケットを分割](bucket_sort.assets/scatter_in_buckets_recursively.png) -商品価格の確率分布を事前に知っている場合、**データの確率分布に基づいて各バケットの価格境界を設定できます**。データ分布を具体的に計算する必要は必ずしもなく、代わりに確率モデルを使用してデータ特性に基づいて近似できることに注意してください。 +商品価格の確率分布をあらかじめ把握しているなら、**データの確率分布に基づいて各バケットの価格境界を設定できます**。なお、データ分布は必ずしも特別に統計を取る必要はなく、データの特徴に応じて何らかの確率モデルで近似することもできます。 -以下の図に示すように、商品価格が正規分布に従うと仮定すると、バケット間でアイテムの分散のバランスを取るために合理的な価格区間を定義できます。 +以下の図に示すように、商品価格が正規分布に従うと仮定すれば、価格区間を合理的に設定でき、それによって商品を各バケットへ均等に分配できます。 -![確率分布に基づくバケット分割](bucket_sort.assets/scatter_in_buckets_distribution.png) +![確率分布に基づいてバケットを分割](bucket_sort.assets/scatter_in_buckets_distribution.png) diff --git a/ja/docs/chapter_sorting/counting_sort.md b/ja/docs/chapter_sorting/counting_sort.md index 3235ec689..2d7fd3f2e 100644 --- a/ja/docs/chapter_sorting/counting_sort.md +++ b/ja/docs/chapter_sorting/counting_sort.md @@ -1,18 +1,18 @@ # 計数ソート -計数ソートは要素の数をカウントすることでソートを実現し、通常は整数配列に適用されます。 +計数ソート(counting sort)は要素数を集計することでソートを実現し、通常は整数配列に適用されます。 -## 簡単な実装 +## 単純な実装 -簡単な例から始めましょう。長さ $n$ の配列 `nums` が与えられ、すべての要素が「非負整数」である場合、計数ソートの全体的な過程は以下の図に示されています。 +まず簡単な例を見てみましょう。長さ $n$ の配列 `nums` が与えられ、その要素はすべて「非負整数」であるとします。計数ソートの全体的な流れを以下の図に示します。 -1. 配列を走査して最大数を見つけ、それを $m$ とし、長さ $m + 1$ の補助配列 `counter` を作成します。 -2. **`counter` を使用して `nums` 内の各数の出現回数をカウントします**。ここで `counter[num]` は数 `num` の出現回数に対応します。カウント方法は簡単で、`nums` を走査し(現在の数を `num` とする)、各ラウンドで `counter[num]` を $1$ 増やします。 -3. **`counter` のインデックスは自然に順序付けられているため、すべての数は本質的にすでにソートされています**。次に、`counter` を走査し、出現順に `nums` を昇順で埋めます。 +1. 配列を走査し、その中の最大値を見つけて $m$ とし、続いて長さ $m + 1$ の補助配列 `counter` を作成します。 +2. **`counter` を用いて `nums` 内の各数値の出現回数を集計します**。ここで `counter[num]` は数値 `num` の出現回数に対応します。集計方法は非常に簡単で、`nums` を走査し(現在の数値を `num` とする)、各回で `counter[num]` を $1$ 増やせばよいです。 +3. **`counter` の各インデックスは自然に順序づけられているため、すべての数値はすでに整列された状態とみなせます**。続いて `counter` を走査し、各数値の出現回数に応じて小さい順に `nums` へ書き戻せば完了です。 -![計数ソートの過程](counting_sort.assets/counting_sort_overview.png) +![計数ソートの流れ](counting_sort.assets/counting_sort_overview.png) -コードは以下の通りです: +コードは以下のとおりです: ```src [file]{counting_sort}-[class]{}-[func]{counting_sort_naive} @@ -20,27 +20,27 @@ !!! note "計数ソートとバケットソートの関係" - バケットソートの観点から、計数ソートにおける計数配列 `counter` の各インデックスをバケットと考え、カウントの過程を要素を対応するバケットに分散させることと考えることができます。本質的に、計数ソートは整数データのためのバケットソートの特別なケースです。 + バケットソートの観点から見ると、計数ソートにおける計数配列 `counter` の各インデックスを 1 つのバケットとみなし、個数を数える過程を各要素を対応するバケットへ振り分ける操作とみなせます。本質的には、計数ソートは整数データにおけるバケットソートの特殊な一例です。 ## 完全な実装 -注意深い読者は気付くかもしれませんが、**入力データがオブジェクトの場合、上記の手順 `3.` は無効です**。入力データが商品オブジェクトで、価格(クラスメンバ変数)で商品をソートしたいとします。しかし、上記のアルゴリズムは結果としてソート済みの価格のみを提供できます。 +注意深い読者なら、**入力データがオブジェクトである場合、上記の手順 `3.` は機能しない**ことに気づくかもしれません。入力データが商品オブジェクトであり、商品価格(クラスのメンバ変数)に基づいて商品をソートしたいとします。しかし上記のアルゴリズムが返せるのは価格のソート結果だけです。 -では、元のデータのソート結果をどのように取得できるでしょうか?まず、`counter` の「前置和」を計算します。名前が示すように、インデックス `i` での前置和 `prefix[i]` は、配列の最初の `i` 個の要素の和に等しいです: +では、元のデータのソート結果を得るにはどうすればよいのでしょうか。まず `counter` の「累積和」を計算します。名前のとおり、インデックス `i` における累積和 `prefix[i]` は、配列の先頭から `i` 番目までの要素の総和に等しくなります: $$ \text{prefix}[i] = \sum_{j=0}^i \text{counter[j]} $$ -**前置和には明確な意味があります。`prefix[num] - 1` は結果配列 `res` における要素 `num` の最後の出現のインデックスを表します**。この情報は重要で、各要素が結果配列のどこに現れるべきかを教えてくれます。次に、元の配列 `nums` の各要素 `num` を逆順で走査し、各反復で以下の2つの手順を実行します。 +**累積和には明確な意味があり、`prefix[num] - 1` は要素 `num` が結果配列 `res` に最後に現れるインデックスを表します**。この情報は非常に重要で、各要素が結果配列のどの位置に現れるべきかを示してくれます。続いて元の配列 `nums` を逆順に走査し、各要素 `num` に対して各反復で次の 2 つの手順を行います。 -1. インデックス `prefix[num] - 1` で配列 `res` に `num` を埋めます。 -2. 前置和 `prefix[num]` を $1$ 減らして、`num` を配置する次のインデックスを取得します。 +1. `num` を配列 `res` のインデックス `prefix[num] - 1` に格納します。 +2. 累積和 `prefix[num]` を $1$ 減らし、次に `num` を配置するインデックスを得ます。 -走査後、配列 `res` にはソートされた結果が含まれ、最後に `res` が元の配列 `nums` を置き換えます。完全な計数ソートの過程は以下の図に示されています。 +走査が完了すると、配列 `res` にソート済みの結果が格納されます。最後に `res` で元の配列 `nums` を上書きすれば完了です。以下の図は完全な計数ソートの流れを示しています。 === "<1>" - ![計数ソートの過程](counting_sort.assets/counting_sort_step1.png) + ![計数ソートの手順](counting_sort.assets/counting_sort_step1.png) === "<2>" ![counting_sort_step2](counting_sort.assets/counting_sort_step2.png) @@ -63,22 +63,22 @@ $$ === "<8>" ![counting_sort_step8](counting_sort.assets/counting_sort_step8.png) -計数ソートの実装コードは以下の通りです: +計数ソートの実装コードは以下のとおりです: ```src [file]{counting_sort}-[class]{}-[func]{counting_sort} ``` -## アルゴリズムの特徴 +## アルゴリズムの特性 -- **時間計算量は $O(n + m)$、非適応ソート**:`nums` と `counter` の走査が含まれ、どちらも線形時間を使用します。一般的に、$n \gg m$ であり、時間計算量は $O(n)$ に近づきます。 -- **空間計算量は $O(n + m)$、非インプレースソート**:長さ $n$ の配列 `res` と長さ $m$ の配列 `counter` をそれぞれ使用します。 -- **安定ソート**:要素が「右から左」の順序で `res` に埋められるため、`nums` の走査を逆順にすることで、等しい要素間の相対位置の変化を防ぎ、安定したソートを実現できます。実際、`nums` を順番に走査しても正しいソート結果を生成できますが、結果は不安定です。 +- **時間計算量は $O(n + m)$、非適応ソート** :`nums` の走査と `counter` の走査が含まれ、いずれも線形時間です。一般には $n \gg m$ であり、時間計算量は $O(n)$ に近づきます。 +- **空間計算量は $O(n + m)$、非インプレースソート**:長さがそれぞれ $n$ と $m$ の配列 `res` と `counter` を利用します。 +- **安定ソート**:`res` に要素を埋める順序が「右から左」であるため、`nums` を逆順に走査することで等しい要素どうしの相対位置が変化するのを防ぎ、安定ソートを実現できます。実際には、`nums` を順方向に走査しても正しいソート結果は得られますが、その結果は安定ではありません。 -## 制限事項 +## 制約 -今までに、計数ソートは非常に巧妙だと感じるかもしれません。単に量をカウントするだけで効率的なソートを実現できるからです。しかし、計数ソートを使用するための前提条件は比較的厳しいです。 +ここまで読むと、計数ソートは非常に巧妙で、個数を数えるだけで効率的なソートを実現できると感じるかもしれません。しかし、計数ソートを利用するための前提条件は比較的厳格です。 -**計数ソートは非負整数にのみ適用できます**。他のタイプのデータに適用したい場合、これらのデータが要素の元の順序を変更することなく非負整数に変換できることを保証する必要があります。例えば、負の整数を含む配列の場合、最初にすべての数に定数を加えて、すべてを正の数に変換し、ソート完了後に元に戻すことができます。 +**計数ソートは非負整数にしか適用できません**。ほかの型のデータに適用したい場合は、それらのデータを非負整数に変換でき、かつ変換の過程で要素間の相対的な大小関係が変わらないことを保証する必要があります。たとえば、負数を含む整数配列に対しては、すべての数値に定数を加えて正数へ変換し、ソート後に元へ戻すことができます。 -**計数ソートは値の範囲が小さい大きなデータセットに適しています**。例えば、上記の例では、$m$ は大きすぎるべきではありません。そうでなければ、あまりにも多くのスペースを占有してしまいます。そして $n \ll m$ の場合、計数ソートは $O(m)$ 時間を使用し、$O(n \log n)$ ソートアルゴリズムより遅い可能性があります。 +**計数ソートはデータ量が多く、値域が小さい場合に適しています**。たとえば上記の例では $m$ が大きすぎてはならず、そうでないと過剰な空間を消費します。また、$n \ll m$ のとき、計数ソートは $O(m)$ 時間を要するため、$O(n \log n)$ のソートアルゴリズムより遅くなる可能性があります。 diff --git a/ja/docs/chapter_sorting/heap_sort.md b/ja/docs/chapter_sorting/heap_sort.md index 24381eacc..b2be0c195 100644 --- a/ja/docs/chapter_sorting/heap_sort.md +++ b/ja/docs/chapter_sorting/heap_sort.md @@ -2,30 +2,30 @@ !!! tip - この節を読む前に、「ヒープ」の章を必ず完了させてください。 + 本節を読む前に、「ヒープ」の章を学習済みであることを確認してください。 -ヒープソートは、ヒープデータ構造に基づく効率的なソートアルゴリズムです。すでに学習した「ヒープの構築」と「要素の抽出」操作を使用してヒープソートを実装できます。 +ヒープソート(heap sort)は、ヒープデータ構造に基づいて実装される効率的なソートアルゴリズムです。すでに学んだ「ヒープ構築操作」と「要素の取り出し操作」を利用してヒープソートを実現できます。 -1. 配列を入力し、最小ヒープを構築します。ここで、最小要素がヒープの頂上に位置します。 -2. 継続的に抽出操作を実行し、抽出された要素を順次記録して、最小から最大までのソート済みリストを取得します。 +1. 配列を入力して最小ヒープを構築すると、このとき最小要素はヒープの頂点にあります。 +2. 取り出し操作を繰り返し実行し、取り出された要素を順に記録すれば、昇順に並んだ列が得られます。 -上記の方法は実現可能ですが、ポップされた要素を格納するための追加の配列が必要で、やや空間を消費します。実際には、通常、より優雅な実装を使用します。 +以上の方法でも実行できますが、取り出した要素を保存するために追加の配列が必要となり、空間をやや無駄にします。実際には、通常はより洗練された実装方法を用います。 ## アルゴリズムの流れ -配列の長さを $n$ とすると、ヒープソートの過程は以下の通りです。 +配列の長さを $n$ とすると、ヒープソートの流れは次図のとおりです。 -1. 配列を入力し、最大ヒープを構築します。この手順の後、最大要素がヒープの頂上に位置します。 -2. ヒープの頂上要素(最初の要素)とヒープの底部要素(最後の要素)を交換します。この交換の後、ヒープの長さを $1$ 減らし、ソート済み要素の数を $1$ 増やします。 -3. ヒープの頂上から開始して、上から下へのsift-down操作を実行します。sift-downの後、ヒープの性質が復元されます。 -4. 手順 `2.` と `3.` を繰り返します。$n - 1$ ラウンドループして、配列のソートを完了します。 +1. 配列を入力して最大ヒープを構築します。完了後、最大要素はヒープの頂点にあります。 +2. ヒープ頂点の要素(最初の要素)とヒープ末尾の要素(最後の要素)を交換します。交換後、ヒープの長さは $1$ 減少し、整列済み要素数は $1$ 増加します。 +3. ヒープ頂点の要素から始めて、上から下へヒープ化操作(sift down)を実行します。ヒープ化が完了すると、ヒープの性質が回復します。 +4. 第 `2.` ステップと第 `3.` ステップを繰り返し実行します。これを $n - 1$ 回繰り返すと、配列の整列が完了します。 !!! tip - 実際、要素抽出操作も手順 `2.` と `3.` を含み、抽出された要素をヒープから削除する追加の手順があります。 + 実際には、要素の取り出し操作にも第 `2.` ステップと第 `3.` ステップが含まれており、要素を取り出す処理が 1 つ加わるだけです。 === "<1>" - ![ヒープソートの過程](heap_sort.assets/heap_sort_step1.png) + ![ヒープソートの手順](heap_sort.assets/heap_sort_step1.png) === "<2>" ![heap_sort_step2](heap_sort.assets/heap_sort_step2.png) @@ -60,14 +60,14 @@ === "<12>" ![heap_sort_step12](heap_sort.assets/heap_sort_step12.png) -コードの実装では、「ヒープ」の章からのsift-down関数 `sift_down()` を使用しました。最大要素が抽出されるにつれてヒープの長さが減少するため、`sift_down()` 関数に長さパラメータ $n$ を追加して、ヒープの現在の有効長を指定する必要があることに注意することが重要です。コードは以下の通りです: +コード実装では、「ヒープ」の章と同じ上から下へのヒープ化 `sift_down()` 関数を使用します。注意すべき点として、ヒープの長さは最大要素を取り出すたびに短くなるため、`sift_down()` 関数に長さパラメータ $n$ を追加し、ヒープの現在の有効な長さを指定する必要があります。コードは以下のとおりです。 ```src [file]{heap_sort}-[class]{}-[func]{heap_sort} ``` -## アルゴリズムの特徴 +## アルゴリズムの特性 -- **時間計算量は $O(n \log n)$、非適応ソート**:ヒープの構築は $O(n)$ 時間を使用します。ヒープから最大要素を抽出するには $O(\log n)$ 時間がかかり、$n - 1$ ラウンドループします。 -- **空間計算量は $O(1)$、インプレースソート**:いくつかのポインタ変数が $O(1)$ 空間を使用します。要素の交換とヒープ化操作は元の配列で実行されます。 -- **非安定ソート**:ヒープの頂上と底部要素の交換中に、等しい要素の相対位置が変わる可能性があります。 +- **時間計算量は $O(n \log n)$、非適応ソート**:ヒープ構築操作には $O(n)$ の時間がかかります。ヒープから最大要素を取り出す時間計算量は $O(\log n)$ であり、これを合計 $n - 1$ 回繰り返します。 +- **空間計算量は $O(1)$、インプレースソート**:いくつかのポインタ変数が使う空間は $O(1)$ です。要素の交換とヒープ化操作はいずれも元の配列上で行われます。 +- **非安定ソート**:ヒープ頂点の要素とヒープ末尾の要素を交換する際、等しい要素どうしの相対位置が変化する可能性があります。 diff --git a/ja/docs/chapter_sorting/index.md b/ja/docs/chapter_sorting/index.md index 7984bd372..d028cb3e8 100644 --- a/ja/docs/chapter_sorting/index.md +++ b/ja/docs/chapter_sorting/index.md @@ -1,9 +1,9 @@ # ソート -![Sorting](../assets/covers/chapter_sorting.jpg) +![ソート](../assets/covers/chapter_sorting.jpg) !!! abstract - ソートは混沌を秩序に変える魔法の鍵のようなもので、データをより効率的に理解し処理することを可能にします。 + ソートは、混沌を秩序へと変える魔法の鍵のようなものであり、データをより効率的に理解し処理することを可能にします。 - 単純な昇順であろうと複雑なカテゴリ配列であろうと、ソートはデータの調和美を明らかにします。 + 単純な昇順であれ、複雑な分類配列であれ、ソートはデータの調和のとれた美しさを私たちに示してくれます。 diff --git a/ja/docs/chapter_sorting/insertion_sort.md b/ja/docs/chapter_sorting/insertion_sort.md index f9d294cb9..8ee3d3965 100644 --- a/ja/docs/chapter_sorting/insertion_sort.md +++ b/ja/docs/chapter_sorting/insertion_sort.md @@ -1,46 +1,46 @@ # 挿入ソート -挿入ソートは、トランプのデッキを手動でソートするプロセスによく似た動作をするシンプルなソートアルゴリズムです。 +挿入ソート(insertion sort)は単純なソートアルゴリズムであり、その動作原理は手作業でトランプの山を整える過程と非常によく似ています。 -具体的には、未ソート区間からベース要素を選択し、その左側のソート済み区間の要素と比較して、要素を正しい位置に挿入します。 +具体的には、未ソート区間から基準要素を 1 つ選び、その要素を左側の整列済み区間の要素と 1 つずつ比較し、正しい位置に挿入します。 -下図は、要素が配列に挿入される方法を示しています。ベース要素を`base`とすると、ターゲットインデックスから`base`までのすべての要素を右に1つずつシフトし、その後`base`をターゲットインデックスに割り当てる必要があります。 +以下の図は、配列に要素を挿入する操作の流れを示しています。基準要素を `base` とすると、目的のインデックスから `base` までのすべての要素を 1 つずつ右に移動し、その後 `base` を目的のインデックスに代入する必要があります。 -![Single insertion operation](insertion_sort.assets/insertion_operation.png) +![1 回の挿入操作](insertion_sort.assets/insertion_operation.png) -## アルゴリズムプロセス +## アルゴリズムの流れ -挿入ソートの全体的なプロセスは下図に示されます。 +挿入ソート全体の流れを以下の図に示します。 -1. 配列の最初の要素をソート済みとみなします。 -2. 2番目の要素を`base`として選択し、正しい位置に挿入して、**最初の2つの要素をソート済みにします**。 -3. 3番目の要素を`base`として選択し、正しい位置に挿入して、**最初の3つの要素をソート済みにします**。 -4. この方法で続行し、最後の反復では、最後の要素を`base`として取り、正しい位置に挿入した後、**すべての要素がソートされます**。 +1. 初期状態では、配列の 1 番目の要素はすでに整列済みです。 +2. 配列の 2 番目の要素を `base` として選び、正しい位置に挿入すると、**配列の先頭 2 要素が整列済み**になります。 +3. 3 番目の要素を `base` として選び、正しい位置に挿入すると、**配列の先頭 3 要素が整列済み**になります。 +4. このように繰り返し、最後のラウンドで最後の要素を `base` として選んで正しい位置に挿入すると、**すべての要素が整列済み**になります。 -![Insertion sort process](insertion_sort.assets/insertion_sort_overview.png) +![挿入ソートの流れ](insertion_sort.assets/insertion_sort_overview.png) -コード例は以下の通りです: +コード例は以下のとおりです。 ```src [file]{insertion_sort}-[class]{}-[func]{insertion_sort} ``` -## アルゴリズムの特性 +## アルゴリズムの特徴 -- **時間計算量は$O(n^2)$、適応ソート**:最悪の場合、各挿入操作には$n - 1$、$n-2$、...、$2$、$1$のループが必要で、合計は$(n - 1) n / 2$となり、時間計算量は$O(n^2)$です。順序付きデータの場合、挿入操作は早期に終了します。入力配列が完全に順序付けられている場合、挿入ソートは最良時間計算量$O(n)$を実現します。 -- **空間計算量は$O(1)$、インプレースソート**:ポインタ$i$と$j$は定数量の追加空間を使用します。 -- **安定ソート**:挿入操作中、等しい要素の右側に要素を挿入し、順序を変更しません。 +- **計算量は $O(n^2)$、適応的ソート**:最悪の場合、各挿入操作ではそれぞれ $n - 1$、$n-2$、$\dots$、$2$、$1$ 回のループが必要であり、合計は $(n - 1) n / 2$ となるため、時間計算量は $O(n^2)$ です。データが整列済みであれば、挿入操作は早期に終了します。入力配列が完全に整列済みである場合、挿入ソートは最良の時間計算量 $O(n)$ に達します。 +- **空間計算量は $O(1)$、インプレースソート**:ポインタ $i$ と $j$ は定数サイズの追加領域しか使用しません。 +- **安定ソート**:挿入操作の過程では、要素を等しい要素の右側に挿入するため、それらの順序は変化しません。 ## 挿入ソートの利点 -挿入ソートの時間計算量は$O(n^2)$で、次に学習するクイックソートの時間計算量は$O(n \log n)$です。挿入ソートはより高い時間計算量を持ちますが、**小さな入力サイズでは通常より高速です**。 +挿入ソートの時間計算量は $O(n^2)$ であり、これから学ぶクイックソートの時間計算量は $O(n \log n)$ です。挿入ソートの時間計算量のほうが大きいにもかかわらず、**データ量が小さい場合には、挿入ソートのほうが通常は高速**です。 -この結論は線形探索と二分探索の結論と似ています。時間計算量が$O(n \log n)$で分割統治戦略に基づくクイックソートなどのアルゴリズムは、多くの場合より多くの単位操作を含みます。小さな入力サイズでは、$n^2$と$n \log n$の数値は近く、計算量が支配的でなく、ラウンドあたりの単位操作数が決定的な役割を果たします。 +この結論は、線形探索と二分探索の適用条件に関する結論と似ています。クイックソートのような $O(n \log n)$ のアルゴリズムは分割統治法に基づくソートアルゴリズムであり、一般により多くの基本演算を含みます。一方、データ量が小さい場合は、$n^2$ と $n \log n$ の値は比較的近く、計算量が支配的ではなくなり、各ラウンドにおける基本演算の回数が決定的な役割を果たします。 -実際、多くのプログラミング言語(Javaなど)は、組み込みソート関数内で挿入ソートを使用しています。一般的なアプローチは:長い配列に対しては、クイックソートなどの分割統治戦略に基づくソートアルゴリズムを使用し、短い配列に対しては挿入ソートを直接使用します。 +実際、多くのプログラミング言語(たとえば Java)の組み込みソート関数では挿入ソートが採用されており、その大まかな考え方は次のとおりです。長い配列にはクイックソートなどの分割統治法に基づくソートアルゴリズムを使い、短い配列には直接挿入ソートを使います。 -バブルソート、選択ソート、挿入ソートはすべて時間計算量$O(n^2)$を持ちますが、実際には、**挿入ソートはバブルソートや選択ソートよりも一般的に使用されます**。主な理由は以下の通りです。 +バブルソート、選択ソート、挿入ソートはいずれも時間計算量が $O(n^2)$ ですが、実際には、**挿入ソートはバブルソートや選択ソートよりもはるかに高い頻度で使われます**。主な理由は次のとおりです。 -- バブルソートは要素交換に基づき、一時変数の使用が必要で、3つの単位操作を含みます;挿入ソートは要素代入に基づき、1つの単位操作のみが必要です。したがって、**バブルソートの計算オーバーヘッドは一般的に挿入ソートよりも高くなります**。 -- 選択ソートの時間計算量は常に$O(n^2)$です。**部分的に順序付けられたデータのセットが与えられた場合、挿入ソートは通常選択ソートよりも効率的です**。 -- 選択ソートは不安定で、マルチレベルソートに適用できません。 +- バブルソートは要素の交換によって実装され、1 つの一時変数を必要とするため、合計で 3 回の基本演算が関わります。これに対して、挿入ソートは要素の代入に基づいており、必要な基本演算は 1 回だけです。したがって、**バブルソートの計算コストは通常、挿入ソートより高くなります**。 +- 選択ソートの時間計算量はどのような場合でも $O(n^2)$ です。**部分的に整列されたデータが与えられた場合、挿入ソートは通常、選択ソートより効率的**です。 +- 選択ソートは安定ではないため、多段ソートには適用できません。 diff --git a/ja/docs/chapter_sorting/merge_sort.md b/ja/docs/chapter_sorting/merge_sort.md index e1b69331f..61cacbd10 100644 --- a/ja/docs/chapter_sorting/merge_sort.md +++ b/ja/docs/chapter_sorting/merge_sort.md @@ -1,23 +1,23 @@ # マージソート -マージソートは分割統治戦略に基づくソートアルゴリズムで、下図に示す「分割」と「マージ」フェーズを含みます。 +マージソート(merge sort)は分割統治戦略に基づくソートアルゴリズムであり、以下の図に示す「分割」と「マージ」の段階から構成されます。 -1. **分割フェーズ**:中点から配列を再帰的に分割し、長い配列のソート問題をより短い配列に変換します。 -2. **マージフェーズ**:サブ配列の長さが1になったときに分割を停止し、その後マージを開始します。2つの短いソート済み配列を連続的により長いソート済み配列にマージし、プロセスが完了するまで続行します。 +1. **分割段階**:再帰によって配列を中点で繰り返し分割し、長い配列のソート問題を短い配列のソート問題へ変換します。 +2. **マージ段階**:部分配列の長さが 1 になったら分割を終了し、マージを開始して、左右 2 つの短いソート済み配列をより長いソート済み配列へと繰り返しマージしていきます。 -![The divide and merge phases of merge sort](merge_sort.assets/merge_sort_overview.png) +![マージソートの分割とマージの段階](merge_sort.assets/merge_sort_overview.png) -## アルゴリズムワークフロー +## アルゴリズムの流れ -下図に示すように、「分割フェーズ」は中点から配列を上から下に2つのサブ配列に再帰的に分割します。 +以下の図に示すように、「分割段階」では配列を上から下へ再帰的に中点で 2 つの部分配列へ分割します。 -1. 中点`mid`を計算し、左サブ配列(区間`[left, mid]`)と右サブ配列(区間`[mid + 1, right]`)を再帰的に分割します。 -2. サブ配列の長さが1になるまでステップ`1.`を再帰的に続行し、その後停止します。 +1. 配列の中点 `mid` を計算し、左部分配列(区間 `[left, mid]` )と右部分配列(区間 `[mid + 1, right]` )を再帰的に分割します。 +2. 手順 `1.` を再帰的に実行し、部分配列区間の長さが 1 になった時点で終了します。 -「マージフェーズ」は左と右のサブ配列を下から上にソート済み配列に結合します。重要なのは、マージが長さ1のサブ配列から開始され、マージフェーズ中に各サブ配列がソートされることです。 +「マージ段階」では左部分配列と右部分配列を下から上へとマージし、1 つのソート済み配列にします。長さ 1 の部分配列からマージを始めるため、この段階の各部分配列はすでに整列されています。 === "<1>" - ![Merge sort process](merge_sort.assets/merge_sort_step1.png) + ![マージソートの手順](merge_sort.assets/merge_sort_step1.png) === "<2>" ![merge_sort_step2](merge_sort.assets/merge_sort_step2.png) @@ -46,12 +46,12 @@ === "<10>" ![merge_sort_step10](merge_sort.assets/merge_sort_step10.png) -マージソートの再帰順序は二分木の後順横断と一致することが観察できます。 +観察すると、マージソートの再帰順序は二分木の後順走査と一致していることがわかります。 -- **後順横断**:まず左のサブツリーを再帰的に横断し、次に右のサブツリーを横断し、最後にルートノードを処理します。 -- **マージソート**:まず左のサブ配列を再帰的に処理し、次に右のサブ配列を処理し、最後にマージを実行します。 +- **後順走査**:まず左部分木を再帰し、次に右部分木を再帰し、最後に根ノードを処理します。 +- **マージソート**:まず左部分配列を再帰し、次に右部分配列を再帰し、最後にマージを処理します。 -マージソートの実装は以下のコードに示されます。`nums`でマージされる区間は`[left, right]`で、`tmp`の対応する区間は`[0, right - left]`であることに注意してください。 +マージソートの実装を以下のコードに示します。注意として、`nums` のマージ対象区間は `[left, right]` であり、`tmp` の対応区間は `[0, right - left]` です。 ```src [file]{merge_sort}-[class]{}-[func]{merge_sort} @@ -59,15 +59,15 @@ ## アルゴリズムの特性 -- **$O(n \log n)$の時間計算量、非適応ソート**:分割により高さ$\log n$の再帰ツリーが作成され、各層で合計$n$回の操作をマージし、全体的な時間計算量は$O(n \log n)$となります。 -- **$O(n)$の空間計算量、非インプレースソート**:再帰深度は$\log n$で、$O(\log n)$のスタックフレーム空間を使用します。マージ操作には補助配列が必要で、追加の$O(n)$空間を使用します。 -- **安定ソート**:マージプロセス中、等しい要素の順序は変更されません。 +- **時間計算量は $O(n \log n)$、非適応型ソート**:分割によって高さ $\log n$ の再帰木が生成され、各層でのマージ操作の総数は $n$ であるため、全体の時間計算量は $O(n \log n)$ です。 +- **空間計算量は $O(n)$、インプレースではないソート**:再帰の深さは $\log n$ であり、サイズ $O(\log n)$ のスタックフレーム領域を使用します。マージ操作は補助配列を用いて実装する必要があり、サイズ $O(n)$ の追加領域を使用します。 +- **安定ソート**:マージの過程では、等しい要素の順序は変化しません。 ## 連結リストのソート -連結リストの場合、マージソートは他のソートアルゴリズムよりも大きな利点があります。**連結リストソートタスクの空間計算量を$O(1)$に最適化できます**。 +連結リストに対しては、マージソートは他のソートアルゴリズムと比べて顕著な利点があり、**連結リストのソート問題の空間計算量を $O(1)$ まで最適化できます** 。 -- **分割フェーズ**:「再帰」の代わりに「反復」を使用して連結リスト分割作業を実行できるため、再帰で使用されるスタックフレーム空間を節約できます。 -- **マージフェーズ**:連結リストでは、ノードの挿入と削除操作は参照(ポインタ)を変更することで実現できるため、マージフェーズ(2つの短い順序付きリストを1つの長い順序付きリストに結合)中に追加のリストを作成する必要がありません。 +- **分割段階**:連結リストの分割は「再帰」の代わりに「反復」で実装できるため、再帰で使用するスタックフレーム領域を省けます。 +- **マージ段階**:連結リストでは、ノードの追加や削除は参照(ポインタ)を変更するだけで実現できるため、マージ段階(2 つの短いソート済み連結リストを 1 つの長いソート済み連結リストにマージすること)では追加の連結リストを作成する必要がありません。 -実装の詳細は比較的複雑で、興味のある読者は関連資料を参照して学習してください。 +具体的な実装の詳細は比較的複雑なので、興味のある読者は関連資料を参照して学習してください。 diff --git a/ja/docs/chapter_sorting/quick_sort.md b/ja/docs/chapter_sorting/quick_sort.md index d7f526e95..ffd26ae45 100644 --- a/ja/docs/chapter_sorting/quick_sort.md +++ b/ja/docs/chapter_sorting/quick_sort.md @@ -1,15 +1,15 @@ # クイックソート -クイックソートは分割統治戦略に基づくソートアルゴリズムで、その効率性と幅広い応用で知られています。 +クイックソート(quick sort)は分割統治戦略に基づくソートアルゴリズムであり、実行効率が高く、広く利用されています。 -クイックソートのコア操作は「ピボット分割」で、配列から要素を「ピボット」として選択し、ピボットより小さいすべての要素をその左側に移動し、ピボットより大きいすべての要素をその右側に移動することを目的としています。具体的に、ピボット分割のプロセスは下図に示されます。 +クイックソートの中核操作は「パーティション」であり、その目的は、配列内のある要素を「基準数」として選び、基準数より小さいすべての要素を左側へ、大きい要素を右側へ移動することです。具体的には、パーティションの流れを下図に示します。 -1. 配列の最も左の要素をピボットとして選択し、2つのポインタ`i`と`j`を初期化して配列の両端をそれぞれ指すようにします。 -2. 各ラウンドで`i`(`j`)を使用してピボットより大きい(小さい)最初の要素を探索し、次にこれら2つの要素を交換するループを設定します。 -3. `i`と`j`が出会うまでステップ`2.`を繰り返し、最後にピボットを2つのサブ配列の境界に交換します。 +1. 配列の最左端の要素を基準数として選び、2 つのポインタ `i` と `j` を初期化して、それぞれ配列の両端を指すようにします。 +2. ループを設定し、各ラウンドで `i`(`j`)を使ってそれぞれ基準数より大きい(小さい)最初の要素を探し、その後この 2 つの要素を交換します。 +3. `i` と `j` が出会うまでステップ `2.` を繰り返し、最後に基準数を 2 つの部分配列の境界へ交換します。 === "<1>" - ![Pivot division process](quick_sort.assets/pivot_division_step1.png) + ![パーティションの手順](quick_sort.assets/pivot_division_step1.png) === "<2>" ![pivot_division_step2](quick_sort.assets/pivot_division_step2.png) @@ -35,66 +35,65 @@ === "<9>" ![pivot_division_step9](quick_sort.assets/pivot_division_step9.png) -ピボット分割後、元の配列は3つの部分に分割されます:左サブ配列、ピボット、右サブ配列で、「左サブ配列の任意の要素 $\leq$ ピボット $\leq$ 右サブ配列の任意の要素」を満たします。したがって、これら2つのサブ配列のみをソートすればよいのです。 +パーティションが完了すると、元の配列は左部分配列、基準数、右部分配列の 3 つに分けられ、「左部分配列の任意の要素 $\leq$ 基準数 $\leq$ 右部分配列の任意の要素」を満たします。したがって、次はこの 2 つの部分配列だけをソートすれば済みます。 !!! note "クイックソートの分割統治戦略" - ピボット分割の本質は、より長い配列のソート問題をより短い2つの配列に簡素化することです。 + パーティションの本質は、長い配列のソート問題を 2 つの短い配列のソート問題へ簡略化することです。 ```src [file]{quick_sort}-[class]{quick_sort}-[func]{partition} ``` -## アルゴリズムプロセス +## アルゴリズムの流れ -クイックソートの全体的なプロセスは下図に示されます。 +クイックソート全体の流れを下図に示します。 -1. まず、元の配列に対して「ピボット分割」を実行し、未ソートの左と右のサブ配列を取得します。 -2. 次に、左と右のサブ配列に対してそれぞれ再帰的に「ピボット分割」を実行します。 -3. サブ配列の長さが1になるまで再帰を続け、配列全体のソートを完了します。 +1. まず、元の配列に対して 1 回「パーティション」を実行し、未ソートの左部分配列と右部分配列を得ます。 +2. 次に、左部分配列と右部分配列に対してそれぞれ再帰的に「パーティション」を実行します。 +3. 部分配列の長さが 1 になるまで再帰を続け、配列全体のソートを完了します。 -![Quick sort process](quick_sort.assets/quick_sort_overview.png) +![クイックソートの流れ](quick_sort.assets/quick_sort_overview.png) ```src [file]{quick_sort}-[class]{quick_sort}-[func]{quick_sort} ``` -## アルゴリズムの特徴 +## アルゴリズムの特性 -- **$O(n \log n)$の時間計算量、非適応ソート**:平均的なケースでは、ピボット分割の再帰レベルは$\log n$で、レベルあたりのループの総数は$n$であり、全体で$O(n \log n)$の時間を使用します。最悪の場合、各ラウンドのピボット分割は長さ$n$の配列を長さ$0$と$n - 1$の2つのサブ配列に分割し、再帰レベル数が$n$に達すると、各レベルのループ数は$n$で、使用される総時間は$O(n^2)$です。 -- **$O(n)$の空間計算量、インプレースソート**:入力配列が完全に逆順の場合、最悪の再帰深度は$n$に達し、$O(n)$のスタックフレーム空間を使用します。ソート操作は追加の配列の助けなしに元の配列で実行されます。 -- **非安定ソート**:ピボット分割の最終ステップで、ピボットは等しい要素の右側に交換される可能性があります。 +- **時間計算量は $O(n \log n)$、非適応型ソート**:平均的な場合、パーティションの再帰の深さは $\log n$ で、各層の総ループ回数は $n$ のため、全体で $O(n \log n)$ 時間を要します。最悪の場合、各回のパーティション操作で長さ $n$ の配列が長さ $0$ と $n - 1$ の 2 つの部分配列に分割され、このとき再帰の深さは $n$ に達し、各層のループ回数は $n$ となるため、全体で $O(n^2)$ 時間を要します。 +- **空間計算量は $O(n)$、インプレースソート**:入力配列が完全な逆順の場合、最悪の再帰深さ $n$ に達し、$O(n)$ のスタックフレーム空間を使用します。ソート操作は元の配列上で行われ、追加の配列は用いません。 +- **非安定ソート**:パーティションの最後のステップで、基準数が等しい要素の右側へ交換される可能性があります。 -## なぜクイックソートは高速なのか +## クイックソートが速い理由 -名前が示すように、クイックソートは効率性の面で一定の利点を持つべきです。クイックソートの平均時間計算量は「マージソート」や「ヒープソート」と同じですが、以下の理由で一般的により効率的です。 +名前からも分かるように、クイックソートは効率面で一定の優位性を持っています。クイックソートの平均時間計算量は「マージソート」や「ヒープソート」と同じですが、通常はクイックソートのほうが高効率であり、主な理由は次のとおりです。 -- **最悪ケースシナリオの低い確率**:クイックソートの最悪時間計算量は$O(n^2)$で、マージソートほど安定していませんが、ほとんどの場合、クイックソートは$O(n \log n)$の時間計算量で動作できます。 -- **高いキャッシュ利用率**:ピボット分割操作中、システムはサブ配列全体をキャッシュにロードできるため、要素により効率的にアクセスできます。対照的に、「ヒープソート」などのアルゴリズムは要素にジャンプ方式でアクセスする必要があり、この特徴を欠いています。 -- **計算量の小さな定数係数**:上記3つのアルゴリズムの中で、クイックソートは比較、代入、交換などの操作の総数が最も少ないです。これは「挿入ソート」が「バブルソート」よりも高速な理由と似ています。 +- **最悪ケースが起こる確率が低い**:クイックソートの最悪時間計算量は $O(n^2)$ で、「マージソート」ほど安定ではありませんが、大半のケースでは $O(n \log n)$ の時間計算量で動作します。 +- **キャッシュ利用効率が高い**:パーティション操作の実行時には、システムが部分配列全体をキャッシュに読み込めるため、要素アクセスの効率が高くなります。一方、「ヒープソート」のようなアルゴリズムは要素へ飛び飛びにアクセスする必要があり、この性質を持ちません。 +- **計算量の定数係数が小さい**:上記 3 つのアルゴリズムの中で、クイックソートは比較、代入、交換などの操作総数が最も少なくなります。これは「挿入ソート」が「バブルソート」より速い理由と似ています。 -## ピボット最適化 +## 基準数の最適化 -**クイックソートの時間効率は特定の入力で劣化する可能性があります**。例えば、入力配列が完全に逆順の場合、最も左の要素をピボットとして選択するため、ピボット分割後、ピボットは配列の右端に交換され、左サブ配列の長さが$n - 1$、右サブ配列の長さが$0$になります。この方法を続けると、各ラウンドのピボット分割でサブ配列の長さが$0$になり、分割統治戦略が失敗し、クイックソートは「バブルソート」に似た形に劣化します。 +**クイックソートは、入力によっては時間効率が低下する可能性があります**。極端な例として、入力配列が完全な逆順であるとします。最左端の要素を基準数として選ぶため、パーティション完了後には基準数が配列の最右端へ交換され、左部分配列の長さが $n - 1$、右部分配列の長さが $0$ になります。この再帰を続けると、各回のパーティション後に必ず一方の部分配列の長さが $0$ となり、分割統治戦略が機能せず、クイックソートは「バブルソート」に近い形へ退化します。 -この状況を避けるため、**ピボット分割でピボット選択戦略を最適化できます**。例えば、要素をランダムに選択してピボットとすることができます。ただし、運が悪く、一貫して最適でないピボットを選択した場合、効率はまだ満足できません。 +この状況をできるだけ避けるため、**パーティションにおける基準数の選び方を最適化できます**。たとえば、ランダムに 1 つの要素を選んで基準数にできます。しかし、運が悪く毎回望ましくない基準数を選んでしまうと、効率は依然として十分ではありません。 -プログラミング言語は通常「疑似乱数」を生成することに注意することが重要です。疑似乱数シーケンスに対して特定のテストケースを構築すると、クイックソートの効率はまだ劣化する可能性があります。 +注意すべき点として、プログラミング言語が通常生成するのは「疑似乱数」です。疑似乱数列に合わせて特定のテストケースを構築すると、クイックソートの効率はやはり劣化する可能性があります。 -さらなる改善のため、3つの候補要素(通常は配列の最初、最後、中点の要素)を選択し、**これら3つの候補要素の中央値をピボットとして使用**できます。この方法で、ピボットが「小さすぎず大きすぎない」確率が大幅に増加します。もちろん、さらに多くの候補要素を選択してアルゴリズムの堅牢性をさらに向上させることもできます。この方法により、時間計算量が$O(n^2)$に劣化する確率が大幅に削減されます。 +さらに改善するために、配列から 3 つの候補要素(通常は先頭、末尾、中間の要素)を選び、**その 3 つの候補要素の中央値を基準数とする**ことができます。こうすると、基準数が「小さすぎず大きすぎもしない」確率が大幅に上がります。もちろん、候補要素をさらに増やして、アルゴリズムの頑健性をいっそう高めることも可能です。この方法を採用すると、時間計算量が $O(n^2)$ まで劣化する確率は大きく下がります。 -サンプルコードは以下の通りです: +コード例を以下に示します。 ```src [file]{quick_sort}-[class]{quick_sort_median}-[func]{partition} ``` -## 末尾再帰最適化 +## 再帰の深さの最適化 -**特定の入力では、クイックソートはより多くの空間を占有する可能性があります**。例えば、完全に順序付けられた入力配列を考えてみましょう。再帰でのサブ配列の長さを$m$とします。各ラウンドのピボット分割で、長さ$0$の左サブ配列と長さ$m - 1$の右サブ配列が生成されます。これは、再帰呼び出しごとに問題サイズが1つの要素のみ減少することを意味し、各レベルの再帰での削減が非常に小さくなります。 -結果として、再帰ツリーの高さは$n − 1$に達する可能性があり、これには$O(n)$のスタックフレーム空間が必要です。 +**一部の入力では、クイックソートは多くの空間を消費する可能性があります**。完全に整列済みの入力配列を例にとり、再帰中の部分配列の長さを $m$ とします。各回のパーティション操作では長さ $0$ の左部分配列と長さ $m - 1$ の右部分配列が生成されます。これは、各再帰呼び出しで減る問題サイズが非常に小さいこと(要素が 1 つ減るだけ)を意味し、再帰木の高さは $n - 1$ に達するため、このとき $O(n)$ のスタックフレーム空間を占有します。 -スタックフレーム空間の蓄積を防ぐため、各ラウンドのピボットソート後に2つのサブ配列の長さを比較し、**より短いサブ配列のみを再帰的にソート**できます。より短いサブ配列の長さは$n / 2$を超えないため、この方法は再帰深度が$\log n$を超えないことを保証し、最悪空間計算量を$O(\log n)$に最適化します。コードは以下の通りです: +スタックフレーム空間の蓄積を防ぐために、各回のパーティション完了後に 2 つの部分配列の長さを比較し、**短いほうの部分配列に対してのみ再帰**を行えます。短い部分配列の長さは $n / 2$ を超えないため、この方法なら再帰の深さを $\log n$ 以下に抑えられ、最悪時の空間計算量を $O(\log n)$ まで最適化できます。コードを以下に示します。 ```src [file]{quick_sort}-[class]{quick_sort_tail_call}-[func]{quick_sort} diff --git a/ja/docs/chapter_sorting/radix_sort.md b/ja/docs/chapter_sorting/radix_sort.md index 6de5dc664..7c0612052 100644 --- a/ja/docs/chapter_sorting/radix_sort.md +++ b/ja/docs/chapter_sorting/radix_sort.md @@ -1,41 +1,41 @@ # 基数ソート -前の節では計数ソートを紹介しました。これは、データサイズ $n$ が大きいがデータ範囲 $m$ が小さいシナリオに適しています。$n = 10^6$ の学生IDをソートする必要があり、各IDが $8$ 桁の数字であるとします。これは、データ範囲 $m = 10^8$ が非常に大きいことを意味します。この場合、計数ソートを使用すると、大量のメモリスペースが必要になります。基数ソートはこの状況を回避できます。 +前節では計数ソートを紹介しました。これは、データ量 $n$ が大きく、データ範囲 $m$ が小さい場合に適しています。$n = 10^6$ 個の学籍番号をソートすると仮定すると、学籍番号は $8$ 桁の数字なので、データ範囲 $m = 10^8$ は非常に大きくなります。計数ソートでは大量のメモリ空間を確保する必要がありますが、基数ソートではこの問題を回避できます。 -基数ソートは計数ソートと同じ核心概念を共有し、要素の頻度をカウントすることでソートします。同時に、基数ソートは数字の桁間の漸進的関係を利用してこれを基盤としています。桁を一度に一つずつ処理してソートし、最終的なソート順序を達成します。 +基数ソート(radix sort)の基本的な考え方は計数ソートと同じで、個数を数えることによってソートを実現します。そのうえで、基数ソートは各桁の段階的な関係を利用し、各桁を順にソートすることで、最終的なソート結果を得ます。 -## アルゴリズムの過程 +## アルゴリズムの流れ -学生IDデータを例として、最下位桁を $1$ 番目、最上位桁を $8$ 番目とすると、基数ソートの過程は以下の図に示されています。 +学籍番号データを例にすると、数字の最下位桁を第 $1$ 位、最上位桁を第 $8$ 位としたとき、基数ソートの流れは次図のようになります。 -1. 桁 $k = 1$ を初期化します。 -2. 学生IDの $k$ 番目の桁に対して「計数ソート」を実行します。完了後、データは $k$ 番目の桁に基づいて最小から最大までソートされます。 -3. $k$ を $1$ 増やし、手順 `2.` に戻って反復を続け、すべての桁がソートされるまで続けます。この時点で過程が終了します。 +1. 桁番号 $k = 1$ を初期化します。 +2. 学籍番号の第 $k$ 位に対して「計数ソート」を実行します。完了すると、データは第 $k$ 位に従って昇順に並びます。 +3. $k$ を $1$ 増やし、手順 `2.` に戻って反復を続けます。すべての桁のソートが完了したら終了します。 -![基数ソートアルゴリズムの過程](radix_sort.assets/radix_sort_overview.png) +![基数ソートのアルゴリズムの流れ](radix_sort.assets/radix_sort_overview.png) -以下、コード実装を詳しく見てみます。基数 $d$ での数 $x$ に対して、その $k$ 番目の桁 $x_k$ を取得するには、以下の計算式を使用できます: +以下ではコード実装を分解して見ていきます。$d$ 進数の数値 $x$ について、その第 $k$ 位 $x_k$ を取得するには、次の計算式を用います。 $$ x_k = \lfloor\frac{x}{d^{k-1}}\rfloor \bmod d $$ -ここで $\lfloor a \rfloor$ は浮動小数点数 $a$ の切り捨てを表し、$\bmod \: d$ は $d$ による剰余を表します。学生IDデータの場合、$d = 10$ で $k \in [1, 8]$ です。 +ここで、$\lfloor a \rfloor$ は浮動小数点数 $a$ の切り捨てを表し、$\bmod \: d$ は $d$ による剰余を表します。学籍番号データでは、$d = 10$ かつ $k \in [1, 8]$ です。 -さらに、$k$ 番目の桁に基づいてソートできるように、計数ソートのコードを少し修正する必要があります: +さらに、数字の第 $k$ 位に基づいてソートできるように、計数ソートのコードを少し変更する必要があります。 ```src [file]{radix_sort}-[class]{}-[func]{radix_sort} ``` -!!! question "なぜ最下位桁から開始するのか?" +!!! question "なぜ最下位桁からソートするのですか?" - 連続するソートラウンドでは、後のラウンドの結果が前のラウンドの結果を上書きします。例えば、最初のラウンドの結果が $a < b$ で、2番目のラウンドが $a > b$ の場合、2番目のラウンドの結果が最初のラウンドの結果を置き換えます。上位桁は下位桁より優先されるため、上位桁の前に下位桁をソートすることが理にかなっています。 + 連続するソートの各ラウンドでは、後のラウンドの結果が前のラウンドの結果を上書きします。たとえば、第1ラウンドで $a < b$ となっていても、第2ラウンドで $a > b$ となれば、第2ラウンドの結果が優先されます。数字では高位の優先度が低位より高いため、先に低位をソートし、その後で高位をソートする必要があります。 ## アルゴリズムの特徴 -計数ソートと比較して、基数ソートはより大きな数値範囲に適していますが、**データが固定桁数で表現でき、桁数があまり大きくないことを前提としています**。例えば、浮動小数点数は桁数 $k$ が大きい可能性があり、時間計算量 $O(nk) \gg O(n^2)$ につながる可能性があるため、基数ソートには適していません。 +計数ソートと比べると、基数ソートは値の範囲が大きい場合に適しています。**ただし、データが固定桁数の形式で表せること、かつ桁数が大きすぎないことが前提です**。たとえば、浮動小数点数は基数ソートに適していません。桁数 $k$ が大きすぎて、時間計算量が $O(nk) \gg O(n^2)$ になる可能性があるためです。 -- **時間計算量は $O(nk)$、非適応ソート**:データサイズを $n$、データが基数 $d$、最大桁数を $k$ とすると、単一桁のソートには $O(n + d)$ 時間がかかり、すべての $k$ 桁のソートには $O((n + d)k)$ 時間がかかります。一般的に、$d$ と $k$ はどちらも比較的小さく、時間計算量は $O(n)$ に近づきます。 -- **空間計算量は $O(n + d)$、非インプレースソート**:計数ソートと同様に、基数ソートは長さ $n$ と $d$ の配列 `res` と `counter` にそれぞれ依存します。 -- **安定ソート**:計数ソートが安定な場合、基数ソートも安定です。計数ソートが不安定な場合、基数ソートは正しいソート順序を保証できません。 +- **時間計算量は $O(nk)$、非適応ソート**:データ量を $n$、データが $d$ 進数、最大桁数を $k$ とすると、ある1桁に対して計数ソートを実行する時間は $O(n + d)$ であり、全 $k$ 桁をソートする時間は $O((n + d)k)$ です。通常、$d$ と $k$ はどちらも比較的小さいため、時間計算量は $O(n)$ に近づきます。 +- **空間計算量は $O(n + d)$、非原地ソート**:計数ソートと同様に、基数ソートでは長さ $n$ と $d$ の配列 `res` と `counter` を補助的に用います。 +- **安定ソート**:計数ソートが安定であれば基数ソートも安定です。計数ソートが不安定な場合、基数ソートでは正しいソート結果を保証できません。 diff --git a/ja/docs/chapter_sorting/selection_sort.md b/ja/docs/chapter_sorting/selection_sort.md index ac5681539..7bbb21425 100644 --- a/ja/docs/chapter_sorting/selection_sort.md +++ b/ja/docs/chapter_sorting/selection_sort.md @@ -1,17 +1,17 @@ # 選択ソート -選択ソートは非常にシンプルな原理で動作します:各反復で未ソート区間から最小要素を選択し、ソート済みセクションの末尾に移動するループを使用します。 +選択ソート(selection sort)の仕組みは非常に単純です。ループを開始し、各ラウンドで未ソート区間から最小の要素を選び、整列済み区間の末尾に配置します。 -配列の長さを$n$とすると、選択ソートのステップは下図に示されます。 +配列の長さを $n$ とすると、選択ソートの手順は次の図のようになります。 -1. 最初に、すべての要素は未ソートで、つまり未ソート(インデックス)区間は$[0, n-1]$です。 -2. 区間$[0, n-1]$の最小要素を選択し、インデックス$0$の要素と交換します。この後、配列の最初の要素がソートされます。 -3. 区間$[1, n-1]$の最小要素を選択し、インデックス$1$の要素と交換します。この後、配列の最初の2つの要素がソートされます。 -4. この方法で続行します。$n - 1$ラウンドの選択と交換の後、最初の$n - 1$個の要素がソートされます。 -5. 残りの唯一の要素は結果的に最大要素であり、ソートする必要がないため、配列はソートされます。 +1. 初期状態では、すべての要素が未ソートであり、未ソートな(インデックス)区間は $[0, n-1]$ です。 +2. 区間 $[0, n-1]$ 内の最小要素を選び、インデックス $0$ の要素と交換します。これにより、配列の先頭 1 要素が整列済みになります。 +3. 区間 $[1, n-1]$ 内の最小要素を選び、インデックス $1$ の要素と交換します。これにより、配列の先頭 2 要素が整列済みになります。 +4. これを繰り返します。$n - 1$ 回の選択と交換を経ると、配列の先頭 $n - 1$ 要素が整列済みになります。 +5. 残った 1 つの要素は必ず最大要素なので、ソートは不要です。これで配列のソートは完了します。 === "<1>" - ![Selection sort process](selection_sort.assets/selection_sort_step1.png) + ![選択ソートの手順](selection_sort.assets/selection_sort_step1.png) === "<2>" ![selection_sort_step2](selection_sort.assets/selection_sort_step2.png) @@ -43,16 +43,16 @@ === "<11>" ![selection_sort_step11](selection_sort.assets/selection_sort_step11.png) -コードでは、$k$を使用して未ソート区間内の最小要素を記録します: +コードでは、$k$ を用いて未ソート区間内の最小要素を記録します。 ```src [file]{selection_sort}-[class]{}-[func]{selection_sort} ``` -## アルゴリズムの特性 +## アルゴリズムの特徴 -- **$O(n^2)$の時間計算量、非適応ソート**:外側ループに$n - 1$回の反復があり、未ソートセクションの長さは最初の反復で$n$から始まり、最後の反復で$2$まで減少します。つまり、各外側ループ反復にはそれぞれ$n$、$n - 1$、$\dots$、$3$、$2$回の内側ループ反復が含まれ、合計は$\frac{(n - 1)(n + 2)}{2}$となります。 -- **$O(1)$の空間計算量、インプレースソート**:ポインタ$i$と$j$で定数の追加空間を使用します。 -- **非安定ソート**:下図に示すように、要素`nums[i]`は等しい要素の右側に交換される可能性があり、相対順序が変わる原因となります。 +- **時間計算量は $O(n^2)$、非適応ソート**:外側のループは合計 $n - 1$ 回です。最初のラウンドの未ソート区間の長さは $n$、最後のラウンドでは $2$ であり、各ラウンドの内側のループ回数はそれぞれ $n$、$n - 1$、$\dots$、$3$、$2$ となります。総和は $\frac{(n - 1)(n + 2)}{2}$ です。 +- **空間計算量は $O(1)$、インプレースソート**:ポインタ $i$ と $j$ は定数サイズの追加領域しか使用しません。 +- **不安定ソート**:次の図のように、要素 `nums[i]` がそれと等しい要素の右側へ交換され、両者の相対的な順序が変わる可能性があります。 -![Selection sort instability example](selection_sort.assets/selection_sort_instability.png) +![選択ソートの不安定な例](selection_sort.assets/selection_sort_instability.png) diff --git a/ja/docs/chapter_sorting/sorting_algorithm.md b/ja/docs/chapter_sorting/sorting_algorithm.md index 574ba9a4b..a299a0c8f 100644 --- a/ja/docs/chapter_sorting/sorting_algorithm.md +++ b/ja/docs/chapter_sorting/sorting_algorithm.md @@ -1,33 +1,33 @@ # ソートアルゴリズム -ソートアルゴリズムは、データセットを特定の順序で配列するために使用されます。ソートアルゴリズムは、順序付けられたデータは通常、より効率的に探索、分析、処理できるため、幅広い応用があります。 +ソートアルゴリズム(sorting algorithm)は、データの集合を特定の順序に従って並べ替えるために用いられます。ソートアルゴリズムは幅広く応用されており、整列済みデータは通常、より効率的に検索、分析、処理できるためです。 -下図に示すように、ソートアルゴリズムのデータ型は整数、浮動小数点数、文字、文字列などです。ソート基準は、数値サイズ、文字ASCII順序、またはカスタム基準など、必要に応じて設定できます。 +下図に示すように、ソートアルゴリズムにおけるデータ型は整数、浮動小数点数、文字、文字列などです。ソートの判定規則は、数値の大小、文字の ASCII コード順、またはカスタムルールなど、要件に応じて設定できます。 -![Data types and comparator examples](sorting_algorithm.assets/sorting_examples.png) +![データ型と判定規則の例](sorting_algorithm.assets/sorting_examples.png) -## 評価次元 +## 評価軸 -**実行効率**:ソートアルゴリズムの時間計算量ができるだけ低いことを期待し、全体的な操作数も少ないこと(時間計算量の定数項を下げる)を望みます。大容量データでは、実行効率が特に重要です。 +**実行効率**:ソートアルゴリズムの時間計算量はできるだけ低く、かつ全体の操作回数も少ないこと(時間計算量における定数項が小さいこと)が望まれます。大量データの場合、実行効率はとりわけ重要です。 -**インプレース性**:名前が示すとおり、インプレースソートは元の配列を直接操作することで実現され、追加のヘルパー配列が不要であるため、メモリを節約します。一般的に、インプレースソートはデータ移動操作が少なく、高速です。 +**インプレース性**:その名のとおり、インプレースソートは元の配列を直接操作して並べ替えを行うため、追加の補助配列を必要とせず、メモリを節約できます。通常、インプレースソートはデータの移動操作が少なく、実行速度もより高速です。 -**安定性**:安定ソートは、ソート後に配列内の等しい要素の相対順序が変わらないことを保証します。 +**安定性**:安定ソートは、並べ替え完了後も、等しい要素の配列内での相対順序が変化しません。 -安定ソートは、マルチキーソートシナリオにおいて必要条件です。学生情報を格納するテーブルがあり、第1列と第2列がそれぞれ名前と年齢であるとします。この場合、不安定ソートは入力データの順序を失う可能性があります: +安定ソートは多段ソートの場面で必要条件となります。学生情報を保存した表があり、第 1 列と第 2 列がそれぞれ氏名と年齢であると仮定します。この場合、不安定ソートによって入力データの順序性が失われる可能性があります。 ```shell -# 入力データは名前でソート済み -# (名前, 年齢) +# 入力データは氏名順にソートされている +# (name, age) ('A', 19) ('B', 18) ('C', 21) ('D', 19) ('E', 23) -# 不安定ソートアルゴリズムを使用してリストを年齢でソートすると仮定すると、 -# 結果は('D', 19)と('A', 19)の相対位置を変更し、 -# 入力データが名前でソート済みであるという性質が失われる +# 不安定ソートアルゴリズムで年齢順にリストを並べ替えると仮定すると、 +# 結果では ('D', 19) と ('A', 19) の相対位置が変わり、 +# 入力データが氏名順である性質が失われる ('B', 18) ('D', 19) ('A', 19) @@ -35,12 +35,12 @@ ('E', 23) ``` -**適応性**:適応ソートは入力データ内の既存の順序情報を活用して計算負荷を削減し、より最適な時間効率を実現します。適応ソートアルゴリズムの最良ケース時間計算量は、通常平均ケース時間計算量よりも優れています。 +**適応性**:適応的ソートは、入力データに既に存在する順序情報を利用して計算量を減らし、より優れた時間効率を実現できます。適応的ソートアルゴリズムの最良時間計算量は、通常、平均時間計算量より優れています。 -**比較ベースまたは非比較ベース**:比較ベースソートは比較演算子($<$、$=$、$>$)に依存して要素の相対順序を決定し、配列全体をソートします。理論的最適時間計算量は$O(n \log n)$です。一方、非比較ソートは比較演算子を使用せず、$O(n)$の時間計算量を実現できますが、汎用性は比較的劣ります。 +**比較ベースかどうか**:比較ベースのソートは、比較演算子($<$、$=$、$>$)に依存して要素の相対順序を判定し、それによって配列全体をソートします。理論上の最良時間計算量は $O(n \log n)$ です。一方、非比較ソートは比較演算子を使用せず、時間計算量は $O(n)$ に達しますが、汎用性は相対的に低くなります。 ## 理想的なソートアルゴリズム -**高速実行、インプレース、安定、適応、汎用**。明らかに、これらのすべての特徴を組み合わせたソートアルゴリズムは今日まで見つかっていません。したがって、ソートアルゴリズムを選択する際は、データの特定の特徴と問題の要件に基づいて決定する必要があります。 +**高速、インプレース、安定、適応的、高い汎用性**。明らかに、これまでのところ、以上のすべての特性を兼ね備えたソートアルゴリズムはまだ見つかっていません。そのため、ソートアルゴリズムを選択する際には、具体的なデータの特徴と問題の要件に応じて判断する必要があります。 -次に、さまざまなソートアルゴリズムを一緒に学び、上記の評価次元に基づいてそれぞれの利点と欠点を分析します。 +次に、さまざまなソートアルゴリズムを一緒に学び、上記の評価軸に基づいて各ソートアルゴリズムの長所と短所を分析していきます。 diff --git a/ja/docs/chapter_sorting/summary.md b/ja/docs/chapter_sorting/summary.md index 16a020028..6346838bd 100644 --- a/ja/docs/chapter_sorting/summary.md +++ b/ja/docs/chapter_sorting/summary.md @@ -1,47 +1,47 @@ # まとめ -### 重要な復習 +### 重要なポイントの振り返り -- バブルソートは隣接する要素を交換することで動作します。フラグを追加して早期リターンを可能にすることで、バブルソートの最良ケースの時間計算量を $O(n)$ に最適化できます。 -- 挿入ソートは、未ソート区間から要素を取り出してソート済み区間の正しい位置に挿入することで各ラウンドをソートします。挿入ソートの時間計算量は $O(n^2)$ ですが、単位あたりの操作が比較的少ないため、少量のデータのソートでは非常に人気があります。 -- クイックソートは歩哨分割操作に基づいています。歩哨分割では、常に最悪のピボットを選ぶ可能性があり、時間計算量が $O(n^2)$ に劣化する可能性があります。中央値やランダムピボットを導入することで、そのような劣化の確率を減らすことができます。末尾再帰は再帰の深さを効果的に減らし、空間計算量を $O(\log n)$ に最適化します。 -- マージソートには分割とマージの2つの段階があり、通常分割統治戦略を体現しています。マージソートでは、配列のソートには補助配列の作成が必要で、空間計算量は $O(n)$ になります。しかし、リストのソートの空間計算量は $O(1)$ に最適化できます。 -- バケットソートは3つの手順から構成されます:データをバケットに分散、各バケット内でのソート、バケット順での結果のマージ。これも分割統治戦略を体現し、非常に大きなデータセットに適しています。バケットソートの鍵はデータの均等分散です。 -- 計数ソートはバケットソートの変形で、各データポイントの出現回数をカウントすることでソートします。計数ソートは限られた範囲のデータを持つ大きなデータセットに適しており、データを正の整数に変換する必要があります。 -- 基数ソートは桁ごとにソートすることでデータを処理し、データが固定長の数値として表現される必要があります。 -- 全体的に、私たちは高効率、安定性、インプレース操作、適応性を持つソートアルゴリズムを求めています。しかし、他のデータ構造やアルゴリズムと同様に、これらすべての条件を同時に満たすソートアルゴリズムは存在しません。実際の応用では、データの特性に基づいて適切なソートアルゴリズムを選択する必要があります。 -- 以下の図は、効率性、安定性、インプレース性、適応性の観点から主流のソートアルゴリズムを比較しています。 +- バブルソートは隣接する要素を交換することで整列を行います。フラグを追加して早期リターンを可能にすると、バブルソートの最良時間計算量を $O(n)$ に最適化できます。 +- 挿入ソートは各ラウンドで未整列区間の要素を整列済み区間の正しい位置に挿入することで整列を完了します。挿入ソートの時間計算量は $O(n^2)$ ですが、基本操作が比較的少ないため、小規模データのソート処理で非常に人気があります。 +- クイックソートは番兵分割操作に基づいて整列を行います。番兵分割では毎回最悪の基準値を選んでしまう可能性があり、その結果、時間計算量は $O(n^2)$ まで劣化することがあります。中央値の基準値やランダムな基準値を導入すると、この劣化の確率を下げられます。短い部分配列を優先して再帰すれば、再帰の深さを効果的に抑え、空間計算量を $O(\log n)$ に最適化できます。 +- マージソートは分割とマージという 2 つの段階からなり、分割統治戦略を典型的に体現しています。マージソートでは配列を整列する際に補助配列の作成が必要で、空間計算量は $O(n)$ です。一方、連結リストを整列する場合の空間計算量は $O(1)$ まで最適化できます。 +- バケットソートはデータのバケット分配、バケット内ソート、結果の結合という 3 つの手順を含みます。これも分割統治戦略を体現しており、データ量が非常に大きい場合に適しています。バケットソートの鍵は、データを平均的に分配することにあります。 +- カウントソートはバケットソートの特例であり、データの出現回数を数えることで整列を行います。カウントソートはデータ量が大きく、かつデータ範囲が限られている場合に適しており、データを正の整数に変換できることが前提です。 +- 基数ソートは各桁ごとの整列によってデータを整列し、データが固定桁数の数値として表せることを前提とします。 +- 総じて言えば、私たちは高効率で、安定で、インプレースで、さらに適応的であるといった利点を備えたソートアルゴリズムを見つけたいと考えます。しかし、ほかのデータ構造やアルゴリズムと同様に、これらすべての条件を同時に満たせるソートアルゴリズムは存在しません。実際の応用では、データの特性に応じて適切なソートアルゴリズムを選ぶ必要があります。 +- 下図では、主流のソートアルゴリズムについて、効率、安定性、インプレース性、適応性などを比較しています。 ![ソートアルゴリズムの比較](summary.assets/sorting_algorithms_comparison.png) ### Q & A -**Q**: ソートアルゴリズムの安定性はいつ必要ですか? +**Q**:ソートアルゴリズムの安定性は、どのような場合に必須ですか? -実際には、オブジェクトの一つの属性に基づいてソートする場合があります。例えば、学生は名前と身長の属性を持ち、多段階ソートを実装することを目指します:最初に名前で `(A, 180) (B, 185) (C, 170) (D, 170)` を取得し、次に身長で。ソートアルゴリズムが不安定なため、`(D, 170) (C, 170) (A, 180) (B, 185)` になってしまう可能性があります。 +現実には、オブジェクトのある属性に基づいて整列することがあります。たとえば、学生には氏名と身長という 2 つの属性があり、多段階のソートを行いたいとします。まず氏名で整列して `(A, 180) (B, 185) (C, 170) (D, 170)` を得て、その後に身長で整列します。ソートアルゴリズムが不安定である場合、結果は `(D, 170) (C, 170) (A, 180) (B, 185)` になる可能性があります。 -学生DとCの位置が交換され、名前の順序性が破られているのが分かります。これは望ましくありません。 +このように、学生 D と C の位置が入れ替わり、氏名に関する順序性が壊れてしまいます。これは望ましくありません。 -**Q**: 歩哨分割での「右から左への検索」と「左から右への検索」の順序を交換できますか? +**Q**:番兵分割において、「右から左へ探索する」順序と「左から右へ探索する」順序は入れ替えられますか? -いいえ、最左要素をピボットとして使用する場合、最初に「右から左への検索」を行い、次に「左から右への検索」を行う必要があります。この結論はやや直観に反するので、理由を分析してみましょう。 +できません。最も左端の要素を基準値とする場合は、必ず先に「右から左へ探索する」を行い、その後に「左から右へ探索する」を行う必要があります。この結論はやや直感に反するので、理由を分析してみましょう。 -歩哨分割 `partition()` の最後のステップは `nums[left]` と `nums[i]` を交換することです。交換後、ピボットの左側の要素はすべてピボット以下になります。**これには最後の交換前に `nums[left] >= nums[i]` が成り立つ必要があります**。「左から右への検索」を最初に行い、ピボットより大きい要素が見つからない場合、**`i == j` でループを終了し、`nums[j] == nums[i] > nums[left]` となる可能性があります**。つまり、最終交換操作はピボットより大きい要素を配列の左端に交換し、歩哨分割を失敗させます。 +番兵分割 `partition()` の最後の手順は、`nums[left]` と `nums[i]` を交換することです。交換が終わると、基準値の左側にある要素はすべて基準値 `<=` になります。**したがって、最後の交換の前に `nums[left] >= nums[i]` が必ず成り立っていなければなりません**。仮に先に「左から右へ探索する」を行うと、基準値より大きい要素が見つからない場合、**`i == j` の時点でループを抜け、このとき `nums[j] == nums[i] > nums[left]` となる可能性があります**。つまり、この最後の交換によって、基準値より大きい要素が配列の最左端へ移されてしまい、番兵分割は失敗します。 -例えば、配列 `[0, 0, 0, 0, 1]` が与えられた場合、最初に「左から右への検索」を行うと、歩哨分割後の配列は `[1, 0, 0, 0, 0]` となり、これは正しくありません。 +たとえば、配列 `[0, 0, 0, 0, 1]` が与えられたとき、先に「左から右へ探索する」を行うと、番兵分割後の配列は `[1, 0, 0, 0, 0]` になります。これは誤った結果です。 -さらに考えると、`nums[right]` をピボットとして選択する場合、まったく逆で、最初に「左から右への検索」を行う必要があります。 +さらに考えると、`nums[right]` を基準値に選ぶ場合はちょうど逆になり、必ず先に「左から右へ探索する」を行う必要があります。 -**Q**: 末尾再帰最適化について、短い配列を選択することで再帰の深さが $\log n$ を超えないことを保証するのはなぜですか? +**Q**:クイックソートの再帰深度最適化について、短い配列を選ぶとなぜ再帰深度が $\log n$ を超えないと保証できるのですか? -再帰の深さは現在リターンしていない再帰メソッドの数です。歩哨分割の各ラウンドは元の配列を2つの副配列に分割します。末尾再帰最適化により、再帰的に続行する副配列の長さは最大でも元の配列長の半分です。最悪の場合常に長さを半分にすると仮定すると、最終的な再帰の深さは $\log n$ になります。 +再帰深度とは、現在まだ戻っていない再帰呼び出しの数のことです。各ラウンドの番兵分割では、元の配列を 2 つの部分配列に分けます。再帰深度の最適化後は、下方向に再帰する部分配列の長さは最大でも元の配列長の半分です。最悪の場合でも毎回半分の長さになると仮定すれば、最終的な再帰深度は $\log n$ になります。 -元のクイックソートを見直すと、より大きな配列を継続的に再帰処理する可能性があり、最悪の場合 $n$、$n - 1$、...、$2$、$1$ で、再帰の深さは $n$ になります。末尾再帰最適化はこのシナリオを回避できます。 +元のクイックソートを振り返ると、長いほうの配列に対して連続して再帰してしまう可能性があり、最悪の場合は $n$、$n - 1$、$\dots$、$2$、$1$ と続き、再帰深度は $n$ になります。再帰深度の最適化により、このような状況を避けられます。 -**Q**: 配列のすべての要素が等しい場合、クイックソートの時間計算量は $O(n^2)$ ですか?この劣化ケースをどう処理すべきですか? +**Q**:配列内のすべての要素が等しい場合、クイックソートの時間計算量は $O(n^2)$ になりますか?このような退化はどう処理すべきですか? -はい。この状況については、歩哨分割を使用して配列をピボットより小さい、等しい、大きいの3つの部分に分割することを検討してください。小さい部分と大きい部分のみを再帰的に進めます。この方法では、すべての入力要素が等しい配列を1ラウンドの歩哨分割だけでソートできます。 +はい。この場合は、番兵分割によって配列を「基準値より小さい」「基準値に等しい」「基準値より大きい」の 3 つの部分に分ける方法を検討できます。下方向に再帰するのは、小さい部分と大きい部分だけです。この方法では、入力要素がすべて等しい配列は、1 回の番兵分割だけで整列を完了できます。 -**Q**: なぜバケットソートの最悪ケース時間計算量は $O(n^2)$ ですか? +**Q**:バケットソートの最悪時間計算量が $O(n^2)$ なのはなぜですか? -最悪の場合、すべての要素が同じバケットに配置されます。これらの要素をソートするために $O(n^2)$ アルゴリズムを使用する場合、時間計算量は $O(n^2)$ になります。 +最悪の場合、すべての要素が同じバケットに振り分けられます。その要素群を整列するのに $O(n^2)$ のアルゴリズムを使えば、時間計算量は $O(n^2)$ になります。 diff --git a/ja/docs/chapter_stack_and_queue/deque.md b/ja/docs/chapter_stack_and_queue/deque.md index caf6536c7..d76851a46 100644 --- a/ja/docs/chapter_stack_and_queue/deque.md +++ b/ja/docs/chapter_stack_and_queue/deque.md @@ -1,25 +1,25 @@ # 両端キュー -キューでは、先頭からの要素の削除や末尾への要素の追加のみが可能です。下図に示すように、両端キュー(deque)はより柔軟性を提供し、先頭と末尾の両方で要素の追加や削除を可能にします。 +キューでは、先頭要素を削除するか末尾に要素を追加することしかできません。次の図に示すように、両端キュー(double-ended queue)はより高い柔軟性を備えており、先頭と末尾の両方で要素の追加や削除を行えます。 ![両端キューの操作](deque.assets/deque_operations.png) -## 両端キューの一般的な操作 +## 両端キューの基本操作 -両端キューの一般的な操作は以下の通りです。具体的なメソッド名は使用するプログラミング言語によって異なります。 +両端キューの基本操作を次の表に示します。具体的なメソッド名は、使用するプログラミング言語によって異なります。

  両端キューの操作効率

-| メソッド名 | 説明 | 時間計算量 | -| ------------- | ------------------ | ------------- | -| `pushFirst()` | 先頭に要素を追加 | $O(1)$ | -| `pushLast()` | 末尾に要素を追加 | $O(1)$ | -| `popFirst()` | 先頭要素を削除 | $O(1)$ | -| `popLast()` | 末尾要素を削除 | $O(1)$ | -| `peekFirst()` | 先頭要素にアクセス | $O(1)$ | -| `peekLast()` | 末尾要素にアクセス | $O(1)$ | +| メソッド名 | 説明 | 時間計算量 | +| -------------- | ---------------- | ---------- | +| `push_first()` | 先頭に要素を追加 | $O(1)$ | +| `push_last()` | 末尾に要素を追加 | $O(1)$ | +| `pop_first()` | 先頭要素を削除 | $O(1)$ | +| `pop_last()` | 末尾要素を削除 | $O(1)$ | +| `peek_first()` | 先頭要素にアクセス | $O(1)$ | +| `peek_last()` | 末尾要素にアクセス | $O(1)$ | -同様に、プログラミング言語で実装された両端キュークラスを直接使用することができます: +同様に、プログラミング言語に組み込み実装されている両端キューのクラスを直接使うこともできます: === "Python" @@ -47,7 +47,7 @@ # 両端キューの長さを取得 size: int = len(deq) - # 両端キューが空かどうかを確認 + # 両端キューが空かどうかを判定 is_empty: bool = len(deq) == 0 ``` @@ -75,7 +75,7 @@ /* 両端キューの長さを取得 */ int size = deque.size(); - /* 両端キューが空かどうかを確認 */ + /* 両端キューが空かどうかを判定 */ bool empty = deque.empty(); ``` @@ -103,7 +103,7 @@ /* 両端キューの長さを取得 */ int size = deque.size(); - /* 両端キューが空かどうかを確認 */ + /* 両端キューが空かどうかを判定 */ boolean isEmpty = deque.isEmpty(); ``` @@ -111,7 +111,7 @@ ```csharp title="deque.cs" /* 両端キューを初期化 */ - // C#では、LinkedListを両端キューとして使用 + // C# では、連結リスト LinkedList を両端キューとして使用する LinkedList deque = new(); /* 要素をエンキュー */ @@ -132,7 +132,7 @@ /* 両端キューの長さを取得 */ int size = deque.Count; - /* 両端キューが空かどうかを確認 */ + /* 両端キューが空かどうかを判定 */ bool isEmpty = deque.Count == 0; ``` @@ -140,7 +140,7 @@ ```go title="deque_test.go" /* 両端キューを初期化 */ - // Goでは、listを両端キューとして使用 + // Go では、list を両端キューとして使用する deque := list.New() /* 要素をエンキュー */ @@ -161,7 +161,7 @@ /* 両端キューの長さを取得 */ size := deque.Len() - /* 両端キューが空かどうかを確認 */ + /* 両端キューが空かどうかを判定 */ isEmpty := deque.Len() == 0 ``` @@ -169,7 +169,7 @@ ```swift title="deque.swift" /* 両端キューを初期化 */ - // Swiftには組み込みの両端キュークラスがないため、Arrayを両端キューとして使用 + // Swift には組み込みの両端キュークラスがないため、Array を両端キューとして使用する var deque: [Int] = [] /* 要素をエンキュー */ @@ -181,17 +181,17 @@ /* 要素にアクセス */ let peekFirst = deque.first! // 先頭要素 - let peekLast = deque.last! // 末尾要素 + let peekLast = deque.last! // 末尾要素 /* 要素をデキュー */ - // Arrayを使用する場合、popFirstの計算量はO(n) + // Array で模擬する場合、popFirst の計算量は O(n) let popFirst = deque.removeFirst() // 先頭要素をデキュー - let popLast = deque.removeLast() // 末尾要素をデキュー + let popLast = deque.removeLast() // 末尾要素をデキュー /* 両端キューの長さを取得 */ let size = deque.count - /* 両端キューが空かどうかを確認 */ + /* 両端キューが空かどうかを判定 */ let isEmpty = deque.isEmpty ``` @@ -199,30 +199,30 @@ ```javascript title="deque.js" /* 両端キューを初期化 */ - // JavaScriptには組み込みの両端キューがないため、Arrayを両端キューとして使用 + // JavaScript には組み込みの両端キューがないため、Array を両端キューとして使用するしかない const deque = []; /* 要素をエンキュー */ deque.push(2); deque.push(5); deque.push(4); - // 注意:unshift()は配列のため時間計算量がO(n) + // 配列であるため、unshift() メソッドの時間計算量は O(n) です deque.unshift(3); deque.unshift(1); /* 要素にアクセス */ - const peekFirst = deque[0]; // 先頭要素 - const peekLast = deque[deque.length - 1]; // 末尾要素 + const peekFirst = deque[0]; + const peekLast = deque[deque.length - 1]; /* 要素をデキュー */ - // 注意:shift()は配列のため時間計算量がO(n) - const popFront = deque.shift(); // 先頭要素をデキュー - const popBack = deque.pop(); // 末尾要素をデキュー + // 配列であるため、shift() メソッドの時間計算量は O(n) です + const popFront = deque.shift(); + const popBack = deque.pop(); /* 両端キューの長さを取得 */ const size = deque.length; - /* 両端キューが空かどうかを確認 */ + /* 両端キューが空かどうかを判定 */ const isEmpty = size === 0; ``` @@ -230,30 +230,30 @@ ```typescript title="deque.ts" /* 両端キューを初期化 */ - // TypeScriptには組み込みの両端キューがないため、Arrayを両端キューとして使用 + // TypeScript には組み込みの両端キューがないため、Array を両端キューとして使用するしかない const deque: number[] = []; /* 要素をエンキュー */ deque.push(2); deque.push(5); deque.push(4); - // 注意:unshift()は配列のため時間計算量がO(n) + // 配列であるため、unshift() メソッドの時間計算量は O(n) です deque.unshift(3); deque.unshift(1); /* 要素にアクセス */ - const peekFirst: number = deque[0]; // 先頭要素 - const peekLast: number = deque[deque.length - 1]; // 末尾要素 + const peekFirst: number = deque[0]; + const peekLast: number = deque[deque.length - 1]; /* 要素をデキュー */ - // 注意:shift()は配列のため時間計算量がO(n) - const popFront: number = deque.shift() as number; // 先頭要素をデキュー - const popBack: number = deque.pop() as number; // 末尾要素をデキュー + // 配列であるため、shift() メソッドの時間計算量は O(n) です + const popFront: number = deque.shift() as number; + const popBack: number = deque.pop() as number; /* 両端キューの長さを取得 */ const size: number = deque.length; - /* 両端キューが空かどうかを確認 */ + /* 両端キューが空かどうかを判定 */ const isEmpty: boolean = size === 0; ``` @@ -261,7 +261,7 @@ ```dart title="deque.dart" /* 両端キューを初期化 */ - // Dartでは、Queueが両端キューとして定義される + // Dart では、Queue は両端キューとして定義されています Queue deque = Queue(); /* 要素をエンキュー */ @@ -282,7 +282,7 @@ /* 両端キューの長さを取得 */ int size = deque.length; - /* 両端キューが空かどうかを確認 */ + /* 両端キューが空かどうかを判定 */ bool isEmpty = deque.isEmpty; ``` @@ -314,50 +314,107 @@ /* 両端キューの長さを取得 */ let size = deque.len(); - /* 両端キューが空かどうかを確認 */ + /* 両端キューが空かどうかを判定 */ let is_empty = deque.is_empty(); ``` === "C" ```c title="deque.c" - // Cには組み込みの両端キューが提供されていません + // C には組み込みの両端キューがありません ``` === "Kotlin" ```kotlin title="deque.kt" + /* 両端キューを初期化 */ + val deque = LinkedList() + /* 要素をエンキュー */ + deque.offerLast(2) // 末尾に追加 + deque.offerLast(5) + deque.offerLast(4) + deque.offerFirst(3) // 先頭に追加 + deque.offerFirst(1) + + /* 要素にアクセス */ + val peekFirst = deque.peekFirst() // 先頭要素 + val peekLast = deque.peekLast() // 末尾要素 + + /* 要素をデキュー */ + val popFirst = deque.pollFirst() // 先頭要素をデキュー + val popLast = deque.pollLast() // 末尾要素をデキュー + + /* 両端キューの長さを取得 */ + val size = deque.size + + /* 両端キューが空かどうかを判定 */ + val isEmpty = deque.isEmpty() ``` +=== "Ruby" + + ```ruby title="deque.rb" + # 両端キューを初期化 + # Ruby には組み込みの両端キューがないため、Array を両端キューとして使用するしかありません + deque = [] + + # 要素をエンキュー + deque << 2 + deque << 5 + deque << 4 + # 配列であるため、Array#unshift メソッドの時間計算量は O(n) です + deque.unshift(3) + deque.unshift(1) + + # 要素にアクセス + peek_first = deque.first + peek_last = deque.last + + # 要素をデキュー + # 配列であるため、 Array#shift メソッドの時間計算量は O(n) です + pop_front = deque.shift + pop_back = deque.pop + + # 両端キューの長さを取得 + size = deque.length + + # 両端キューが空かどうかを判定 + is_empty = size.zero? + ``` + +??? pythontutor "実行の可視化" + + https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%0A%20%20%20%20deq%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20deq.append%282%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E5%B0%BE%0A%20%20%20%20deq.append%285%29%0A%20%20%20%20deq.append%284%29%0A%20%20%20%20deq.appendleft%283%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E9%A6%96%0A%20%20%20%20deq.appendleft%281%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20deq%5B0%5D%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%20%20%20%20rear%20%3D%20deq%5B-1%5D%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%20rear%20%3D%22,%20rear%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop_front%20%3D%20deq.popleft%28%29%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_front%20%3D%22,%20pop_front%29%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%20%20%20%20pop_rear%20%3D%20deq.pop%28%29%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_rear%20%3D%22,%20pop_rear%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28deq%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28deq%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + ## 両端キューの実装 * -両端キューの実装は通常のキューの実装と似ており、連結リストまたは配列を基盤となるデータ構造として使用できます。 +両端キューの実装はキューと似ており、連結リストまたは配列を基盤となるデータ構造として選べます。 ### 双方向連結リストに基づく実装 -前節で、通常の単一連結リストを使ってキューを実装したことを思い出してください。これは先頭からの削除(デキュー操作に対応)と末尾への新しい要素の追加(エンキュー操作に対応)を便利に行えるためでした。 +前節を振り返ると、通常の単方向連結リストを使ってキューを実装しました。これは、先頭ノードの削除(デキューに対応)と末尾ノードの後ろへの新規ノード追加(エンキューに対応)を容易に行えるためです。 -両端キューでは、先頭と末尾の両方でエンキューとデキュー操作を実行できます。つまり、両端キューは逆方向の操作も実装する必要があります。このため、両端キューの基盤となるデータ構造として「双方向連結リスト」を使用します。 +両端キューでは、先頭と末尾のどちらでもエンキューとデキューを行えます。言い換えると、両端キューではもう一方の対称方向の操作も実装する必要があります。そのため、両端キューの基盤データ構造として「双方向連結リスト」を採用します。 -下図に示すように、双方向連結リストの先頭ノードと末尾ノードをそれぞれ両端キューの前端と後端として扱い、両端でのノードの追加と削除機能を実装します。 +次の図に示すように、双方向連結リストの先頭ノードと末尾ノードを両端キューの先頭と末尾と見なし、両端でノードを追加および削除する機能を実現します。 === "LinkedListDeque" - ![双方向連結リストによる両端キューのエンキューとデキュー操作の実装](deque.assets/linkedlist_deque_step1.png) + ![連結リストによる両端キューのエンキューとデキュー](deque.assets/linkedlist_deque_step1.png) -=== "pushLast()" +=== "push_last()" ![linkedlist_deque_push_last](deque.assets/linkedlist_deque_step2_push_last.png) -=== "pushFirst()" +=== "push_first()" ![linkedlist_deque_push_first](deque.assets/linkedlist_deque_step3_push_first.png) -=== "popLast()" +=== "pop_last()" ![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_step4_pop_last.png) -=== "popFirst()" +=== "pop_first()" ![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_step5_pop_first.png) -実装コードは以下の通りです: +実装コードは次のとおりです: ```src [file]{linkedlist_deque}-[class]{linked_list_deque}-[func]{} @@ -365,24 +422,24 @@ ### 配列に基づく実装 -下図に示すように、配列でキューを実装するのと同様に、循環配列を使って両端キューを実装することもできます。 +次の図に示すように、配列によるキュー実装と同様に、循環配列を使って両端キューを実装することもできます。 === "ArrayDeque" - ![配列による両端キューのエンキューとデキュー操作の実装](deque.assets/array_deque_step1.png) + ![配列による両端キューのエンキューとデキュー](deque.assets/array_deque_step1.png) -=== "pushLast()" +=== "push_last()" ![array_deque_push_last](deque.assets/array_deque_step2_push_last.png) -=== "pushFirst()" +=== "push_first()" ![array_deque_push_first](deque.assets/array_deque_step3_push_first.png) -=== "popLast()" +=== "pop_last()" ![array_deque_pop_last](deque.assets/array_deque_step4_pop_last.png) -=== "popFirst()" +=== "pop_first()" ![array_deque_pop_first](deque.assets/array_deque_step5_pop_first.png) -実装では「前端エンキュー」と「後端デキュー」のメソッドを追加するだけです: +キュー実装を土台として、「先頭へのエンキュー」と「末尾からのデキュー」のメソッドを追加するだけで済みます: ```src [file]{array_deque}-[class]{array_deque}-[func]{} @@ -390,6 +447,6 @@ ## 両端キューの応用 -両端キューはスタックとキューの両方のロジックを組み合わせているため、**それぞれのすべてのユースケースを実装でき、より大きな柔軟性を提供します**。 +両端キューはスタックとキューの両方の論理を備えているため、**これら 2 つのすべての応用場面を実現でき、さらに高い自由度を提供します**。 -ソフトウェアの「元に戻す」機能は通常スタックを使って実装されることを知っています:システムは各変更操作をスタックに`push`し、次に`pop`して元に戻すことを実装します。しかし、システムリソースの制限を考慮して、ソフトウェアは元に戻すステップの数を制限することがよくあります(例えば、最後の50ステップのみを許可)。スタックの長さが50を超えた場合、ソフトウェアはスタックの底部(キューの前端)で削除操作を実行する必要があります。**しかし、通常のスタックではこの機能を実行できないため、両端キューが必要になります**。「元に戻す」のコアロジックは依然としてスタックの後入れ先出し原則に従いますが、両端キューはより柔軟にいくつかの追加ロジックを実装できることに注意してください。 +私たちが知っているように、ソフトウェアの「元に戻す」機能は通常スタックを使って実装されます。システムは変更操作を毎回スタックに `push` し、その後 `pop` によって取り消しを実現します。しかし、システム資源の制約を考慮すると、通常ソフトウェアは取り消し可能な手数を制限します(たとえば $50$ 手まで保存可能)。スタックの長さが $50$ を超えると、ソフトウェアはスタックの底部(先頭)で削除操作を行う必要があります。**しかしスタックではこの機能を実現できないため、この場合はスタックの代わりに両端キューを使用する必要があります**。なお、「元に戻す」の中核ロジック自体は依然としてスタックの後入れ先出し原則に従っており、両端キューは追加のロジックをより柔軟に実装できるだけです。 diff --git a/ja/docs/chapter_stack_and_queue/index.md b/ja/docs/chapter_stack_and_queue/index.md index a8aaace40..c2411956c 100644 --- a/ja/docs/chapter_stack_and_queue/index.md +++ b/ja/docs/chapter_stack_and_queue/index.md @@ -4,6 +4,6 @@ !!! abstract - スタックは積み重ねられた猫のようなもので、キューは一列に並んだ猫のようなものです。 - - それらはそれぞれ、後入先出(LIFO)と先入先出(FIFO)の論理関係を表しています。 + スタックは猫を積み重ねるようなもので、キューは猫が列に並ぶようなものです。 + + 両者はそれぞれ、後入れ先出しと先入れ先出しの論理関係を表します。 diff --git a/ja/docs/chapter_stack_and_queue/queue.md b/ja/docs/chapter_stack_and_queue/queue.md index d1dc7b080..996ba59bf 100644 --- a/ja/docs/chapter_stack_and_queue/queue.md +++ b/ja/docs/chapter_stack_and_queue/queue.md @@ -1,24 +1,24 @@ # キュー -キューは、先入先出(FIFO)ルールに従う線形データ構造です。名前が示すように、キューは行列の現象をシミュレートし、新参者は列の後ろに並び、前の人が最初に列を離れます。 +キュー(queue)は、先入れ先出しの規則に従う線形データ構造です。名前のとおり、キューは順番待ちの現象を模したもので、新しく来た人は絶えずキュー末尾に加わり、キュー先頭にいる人から順に離れていきます。 -下図に示すように、キューの前面を「ヘッド」、後面を「テール」と呼びます。キューの後ろに要素を追加する操作を「エンキュー」、前から要素を削除する操作を「デキュー」と呼びます。 +下図のように、キューの先頭を「キュー先頭」、末尾を「キュー末尾」と呼びます。要素をキュー末尾に加える操作を「エンキュー」、キュー先頭の要素を削除する操作を「デキュー」と呼びます。 -![キューの先入先出ルール](queue.assets/queue_operations.png) +![キューの先入れ先出し規則](queue.assets/queue_operations.png) -## キューの一般的な操作 +## キューの基本操作 -キューの一般的な操作を下表に示します。メソッド名はプログラミング言語によって異なる場合があることに注意してください。ここでは、スタックで使用したのと同じ命名規則を使用します。 +キューの基本操作を以下の表に示します。なお、メソッド名はプログラミング言語によって異なる場合があります。ここではスタックと同じ命名を採用します。

  キュー操作の効率

-| メソッド名 | 説明 | 時間計算量 | -| ----------- | -------------------------------------- | --------------- | -| `push()` | 要素をエンキュー、テールに追加 | $O(1)$ | -| `pop()` | ヘッド要素をデキュー | $O(1)$ | -| `peek()` | ヘッド要素にアクセス | $O(1)$ | +| メソッド名 | 説明 | 時間計算量 | +| -------- | ---------------------------- | ---------- | +| `push()` | 要素をエンキューし、キュー末尾に追加する | $O(1)$ | +| `pop()` | キュー先頭の要素をデキューする | $O(1)$ | +| `peek()` | キュー先頭の要素にアクセスする | $O(1)$ | -プログラミング言語で用意されているキュークラスを直接使用できます: +プログラミング言語に用意された既存のキュークラスをそのまま利用できます: === "Python" @@ -26,8 +26,8 @@ from collections import deque # キューを初期化 - # Pythonでは、一般的にdequeクラスをキューとして使用します - # queue.Queue()は純粋なキュークラスですが、使いにくいため推奨されません + # Python では、通常は双方向キュークラス deque をキューとして使用する + # queue.Queue() は純粋なキュークラスだが、やや使いにくいため非推奨 que: deque[int] = deque() # 要素をエンキュー @@ -37,7 +37,7 @@ que.append(5) que.append(4) - # 最初の要素にアクセス + # キュー先頭の要素にアクセス front: int = que[0] # 要素をデキュー @@ -46,7 +46,7 @@ # キューの長さを取得 size: int = len(que) - # キューが空かどうかチェック + # キューが空かどうかを判定 is_empty: bool = len(que) == 0 ``` @@ -63,7 +63,7 @@ queue.push(5); queue.push(4); - /* 最初の要素にアクセス */ + /* キュー先頭の要素にアクセス */ int front = queue.front(); /* 要素をデキュー */ @@ -72,7 +72,7 @@ /* キューの長さを取得 */ int size = queue.size(); - /* キューが空かどうかチェック */ + /* キューが空かどうかを判定 */ bool empty = queue.empty(); ``` @@ -89,7 +89,7 @@ queue.offer(5); queue.offer(4); - /* 最初の要素にアクセス */ + /* キュー先頭の要素にアクセス */ int peek = queue.peek(); /* 要素をデキュー */ @@ -98,7 +98,7 @@ /* キューの長さを取得 */ int size = queue.size(); - /* キューが空かどうかチェック */ + /* キューが空かどうかを判定 */ boolean isEmpty = queue.isEmpty(); ``` @@ -115,7 +115,7 @@ queue.Enqueue(5); queue.Enqueue(4); - /* 最初の要素にアクセス */ + /* キュー先頭の要素にアクセス */ int peek = queue.Peek(); /* 要素をデキュー */ @@ -124,7 +124,7 @@ /* キューの長さを取得 */ int size = queue.Count; - /* キューが空かどうかチェック */ + /* キューが空かどうかを判定 */ bool isEmpty = queue.Count == 0; ``` @@ -132,7 +132,7 @@ ```go title="queue_test.go" /* キューを初期化 */ - // Goでは、listをキューとして使用 + // Go では、list をキューとして使用する queue := list.New() /* 要素をエンキュー */ @@ -142,7 +142,7 @@ queue.PushBack(5) queue.PushBack(4) - /* 最初の要素にアクセス */ + /* キュー先頭の要素にアクセス */ peek := queue.Front() /* 要素をデキュー */ @@ -152,7 +152,7 @@ /* キューの長さを取得 */ size := queue.Len() - /* キューが空かどうかチェック */ + /* キューが空かどうかを判定 */ isEmpty := queue.Len() == 0 ``` @@ -160,7 +160,7 @@ ```swift title="queue.swift" /* キューを初期化 */ - // Swiftには組み込みのキュークラスがないため、Arrayをキューとして使用 + // Swift には組み込みのキュークラスがないため、Array をキューとして使える var queue: [Int] = [] /* 要素をエンキュー */ @@ -170,17 +170,17 @@ queue.append(5) queue.append(4) - /* 最初の要素にアクセス */ + /* キュー先頭の要素にアクセス */ let peek = queue.first! /* 要素をデキュー */ - // 配列なので、removeFirstの計算量はO(n) + // 配列であるため、removeFirst の計算量は O(n) let pool = queue.removeFirst() /* キューの長さを取得 */ let size = queue.count - /* キューが空かどうかチェック */ + /* キューが空かどうかを判定 */ let isEmpty = queue.isEmpty ``` @@ -188,7 +188,7 @@ ```javascript title="queue.js" /* キューを初期化 */ - // JavaScriptには組み込みのキューがないため、Arrayをキューとして使用 + // JavaScript には組み込みのキューがないため、Array をキューとして使える const queue = []; /* 要素をエンキュー */ @@ -198,17 +198,17 @@ queue.push(5); queue.push(4); - /* 最初の要素にアクセス */ + /* キュー先頭の要素にアクセス */ const peek = queue[0]; /* 要素をデキュー */ - // 基礎構造が配列なので、shift()メソッドの時間計算量はO(n) + // 基盤は配列であるため、shift() メソッドの時間計算量は O(n) const pop = queue.shift(); /* キューの長さを取得 */ const size = queue.length; - /* キューが空かどうかチェック */ + /* キューが空かどうかを判定 */ const empty = queue.length === 0; ``` @@ -216,7 +216,7 @@ ```typescript title="queue.ts" /* キューを初期化 */ - // TypeScriptには組み込みのキューがないため、Arrayをキューとして使用 + // TypeScript には組み込みのキューがないため、Array をキューとして使える const queue: number[] = []; /* 要素をエンキュー */ @@ -226,17 +226,17 @@ queue.push(5); queue.push(4); - /* 最初の要素にアクセス */ + /* キュー先頭の要素にアクセス */ const peek = queue[0]; /* 要素をデキュー */ - // 基礎構造が配列なので、shift()メソッドの時間計算量はO(n) + // 基盤は配列であるため、shift() メソッドの時間計算量は O(n) const pop = queue.shift(); /* キューの長さを取得 */ const size = queue.length; - /* キューが空かどうかチェック */ + /* キューが空かどうかを判定 */ const empty = queue.length === 0; ``` @@ -244,7 +244,7 @@ ```dart title="queue.dart" /* キューを初期化 */ - // DartのQueueクラスは双方向キューですが、キューとして使用できます + // Dart では、キュークラス Qeque は双方向キューであり、キューとしても使用できる Queue queue = Queue(); /* 要素をエンキュー */ @@ -254,7 +254,7 @@ queue.add(5); queue.add(4); - /* 最初の要素にアクセス */ + /* キュー先頭の要素にアクセス */ int peek = queue.first; /* 要素をデキュー */ @@ -263,7 +263,7 @@ /* キューの長さを取得 */ int size = queue.length; - /* キューが空かどうかチェック */ + /* キューが空かどうかを判定 */ bool isEmpty = queue.isEmpty; ``` @@ -271,7 +271,7 @@ ```rust title="queue.rs" /* 双方向キューを初期化 */ - // Rustでは、双方向キューを通常のキューとして使用 + // Rust では双方向キューを通常のキューとして使う let mut deque: VecDeque = VecDeque::new(); /* 要素をエンキュー */ @@ -281,7 +281,7 @@ deque.push_back(5); deque.push_back(4); - /* 最初の要素にアクセス */ + /* キュー先頭の要素にアクセス */ if let Some(front) = deque.front() { } @@ -292,32 +292,84 @@ /* キューの長さを取得 */ let size = deque.len(); - /* キューが空かどうかチェック */ + /* キューが空かどうかを判定 */ let is_empty = deque.is_empty(); ``` === "C" ```c title="queue.c" - // Cは組み込みのキューを提供していません + // C には組み込みのキューがない ``` === "Kotlin" ```kotlin title="queue.kt" + /* キューを初期化 */ + val queue = LinkedList() + /* 要素をエンキュー */ + queue.offer(1) + queue.offer(3) + queue.offer(2) + queue.offer(5) + queue.offer(4) + + /* キュー先頭の要素にアクセス */ + val peek = queue.peek() + + /* 要素をデキュー */ + val pop = queue.poll() + + /* キューの長さを取得 */ + val size = queue.size + + /* キューが空かどうかを判定 */ + val isEmpty = queue.isEmpty() ``` +=== "Ruby" + + ```ruby title="queue.rb" + # キューを初期化 + # Ruby 組み込みのキュー(Thread::Queue) には peek と走査メソッドがないため、Array をキューとして使える + queue = [] + + # 要素をエンキュー + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + + # キュー先頭の要素にアクセス + peek = queue.first + + # 要素をデキュー + # 注意:配列であるため、Array#shift メソッドの時間計算量は O(n) + pop = queue.shift + + # キューの長さを取得 + size = queue.length + + # キューが空かどうかを判定 + is_empty = queue.empty? + ``` + +??? pythontutor "可視化実行" + + https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%98%9F%E5%88%97%0A%20%20%20%20%23%20%E5%9C%A8%20Python%20%E4%B8%AD%EF%BC%8C%E6%88%91%E4%BB%AC%E4%B8%80%E8%88%AC%E5%B0%86%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%B1%BB%20deque%20%E7%9C%8B%E4%BD%9C%E9%98%9F%E5%88%97%E4%BD%BF%E7%94%A8%0A%20%20%20%20%23%20%E8%99%BD%E7%84%B6%20queue.Queue%28%29%20%E6%98%AF%E7%BA%AF%E6%AD%A3%E7%9A%84%E9%98%9F%E5%88%97%E7%B1%BB%EF%BC%8C%E4%BD%86%E4%B8%8D%E5%A4%AA%E5%A5%BD%E7%94%A8%0A%20%20%20%20que%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20que.append%281%29%0A%20%20%20%20que.append%283%29%0A%20%20%20%20que.append%282%29%0A%20%20%20%20que.append%285%29%0A%20%20%20%20que.append%284%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20que%5B0%5D%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop%20%3D%20que.popleft%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%90%8E%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28que%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28que%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + ## キューの実装 -キューを実装するには、一方の端で要素を追加し、もう一方の端で要素を削除できるデータ構造が必要です。連結リストと配列の両方がこの要件を満たします。 +キューを実装するには、一方の端で要素を追加し、もう一方の端で要素を削除できるデータ構造が必要です。連結リストと配列はいずれもこの条件を満たします。 -### 連結リストベースの実装 +### 連結リストに基づく実装 -下図に示すように、連結リストの「ヘッドノード」と「テールノード」をそれぞれキューの「フロント」と「リア」と考えることができます。ノードは後ろでのみ追加でき、前でのみ削除できるように規定されています。 +下図のように、連結リストの「先頭ノード」と「末尾ノード」をそれぞれ「キュー先頭」と「キュー末尾」とみなし、キュー末尾ではノードの追加のみ、キュー先頭ではノードの削除のみを行うようにします。 === "LinkedListQueue" - ![連結リストによるキュー実装のエンキューとデキュー操作](queue.assets/linkedlist_queue_step1.png) + ![連結リストでキューを実装したエンキューとデキュー操作](queue.assets/linkedlist_queue_step1.png) === "push()" ![linkedlist_queue_push](queue.assets/linkedlist_queue_step2_push.png) @@ -325,27 +377,27 @@ === "pop()" ![linkedlist_queue_pop](queue.assets/linkedlist_queue_step3_pop.png) -以下は、連結リストを使用してキューを実装するコードです: +以下は連結リストでキューを実装するコードです: ```src [file]{linkedlist_queue}-[class]{linked_list_queue}-[func]{} ``` -### 配列ベースの実装 +### 配列に基づく実装 -配列の最初の要素を削除する時間計算量は$O(n)$で、デキュー操作が非効率になります。しかし、この問題は以下のように巧妙に回避できます。 +配列で先頭要素を削除する時間計算量は $O(n)$ であり、そのままではデキュー操作の効率が低くなります。しかし、次の巧妙な方法によってこの問題を回避できます。 -変数`front`を使用してフロント要素のインデックスを示し、変数`size`を維持してキューの長さを記録します。`rear = front + size`を定義し、これはテール要素の直後の位置を指します。 +変数 `front` を用いてキュー先頭要素のインデックスを指し、さらに変数 `size` でキューの長さを記録できます。`rear = front + size` と定義すると、この式で得られる `rear` はキュー末尾要素の次の位置を指します。 -この設計により、**配列内の要素の有効な間隔は`[front, rear - 1]`です**。各操作の実装方法を下図に示します。 +この設計に基づくと、**配列内で要素を含む有効区間は `[front, rear - 1]`** となります。各種操作の実装方法を下図に示します。 -- エンキュー操作:入力要素を`rear`インデックスに割り当て、`size`を1増加させます。 -- デキュー操作:単に`front`を1増加させ、`size`を1減少させます。 +- エンキュー操作:入力要素を `rear` の位置に代入し、`size` を 1 増やします。 +- デキュー操作:`front` を 1 増やし、`size` を 1 減らすだけです。 -エンキューとデキュー操作は両方とも単一の操作のみを必要とし、それぞれの時間計算量は$O(1)$です。 +このように、エンキューとデキューはいずれも 1 回の操作だけで済み、時間計算量はともに $O(1)$ です。 === "ArrayQueue" - ![配列によるキュー実装のエンキューとデキュー操作](queue.assets/array_queue_step1.png) + ![配列でキューを実装したエンキューとデキュー操作](queue.assets/array_queue_step1.png) === "push()" ![array_queue_push](queue.assets/array_queue_step2_push.png) @@ -353,19 +405,19 @@ === "pop()" ![array_queue_pop](queue.assets/array_queue_step3_pop.png) -問題に気づくかもしれません:エンキューとデキュー操作が継続的に実行されると、`front`と`rear`の両方が右に移動し、**最終的に配列の末尾に到達してそれ以上移動できなくなります**。これを解決するために、配列を「循環配列」として扱い、配列の末尾を先頭に接続します。 +ここで 1 つ問題があります。エンキューとデキューを繰り返すと、`front` と `rear` はどちらも右へ移動し続け、**配列の末尾に達するとそれ以上進めなくなります**。この問題を解決するために、配列を先頭と末尾がつながった「環状配列」とみなします。 -循環配列では、`front`または`rear`が末尾に到達すると、配列の先頭にループバックする必要があります。この循環パターンは、以下のコードに示すように「剰余演算」で実現できます: +環状配列では、`front` または `rear` が配列末尾を越えたときに、直ちに配列先頭へ戻って走査を続けられるようにする必要があります。この周期的な規則は「剰余演算」によって実現できます。コードは次のとおりです: ```src [file]{array_queue}-[class]{array_queue}-[func]{} ``` -上記のキュー実装にはまだ制限があります:長さが固定されています。しかし、この問題は解決が困難ではありません。配列を必要に応じて自動拡張できる動的配列に置き換えることができます。興味のある読者は自分で実装してみてください。 +上記の実装によるキューにも制約があり、長さを可変にできません。しかし、この問題の解決は難しくなく、配列を動的配列に置き換えれば容量拡張の仕組みを導入できます。興味があれば自分で実装してみてください。 -2つの実装の比較はスタックの場合と一貫しており、ここでは繰り返しません。 +2 つの実装の比較に関する結論はスタックの場合と同じなので、ここでは繰り返しません。 ## キューの典型的な応用 -- **Amazonの注文**:買い物客が注文を行った後、これらの注文はキューに参加し、システムは順番に処理します。独身の日などのイベント中は、短時間で大量の注文が生成され、高い同時実行性がエンジニアにとって重要な課題となります。 -- **様々なToDoリスト**:「先着順」機能が必要なシナリオ、例えばプリンターのタスクキューやレストランの配達キューなど、キューで処理順序を効果的に維持できます。 +- **淘宝の注文**。購入者が注文すると、その注文はキューに追加され、システムは順番に従って注文を処理します。ダブルイレブンの期間には短時間で膨大な注文が発生するため、高並行性がエンジニアにとって重点的に解決すべき課題になります。 +- **各種の待機事項**。先着順の機能を実現する必要があるあらゆる場面、たとえばプリンターのジョブキューや飲食店の配膳キューなどでは、キューによって処理順序を効果的に維持できます。 diff --git a/ja/docs/chapter_stack_and_queue/stack.md b/ja/docs/chapter_stack_and_queue/stack.md index 0101cd1b3..75529a332 100644 --- a/ja/docs/chapter_stack_and_queue/stack.md +++ b/ja/docs/chapter_stack_and_queue/stack.md @@ -1,51 +1,51 @@ # スタック -スタックは、後入先出(LIFO)の原則に従う線形データ構造です。 +スタック(stack)は、後入れ先出しの論理に従う線形データ構造です。 -スタックをテーブル上の皿の山に例えることができます。底の皿にアクセスするには、まず上の皿を取り除く必要があります。皿を様々な種類の要素(整数、文字、オブジェクトなど)に置き換えることで、スタックと呼ばれるデータ構造を得ることができます。 +スタックは机の上に積まれた皿の山にたとえられます。1回に1枚の皿しか動かせないとすると、いちばん下の皿を取り出すには、上にある皿を順番にどかす必要があります。この皿をさまざまな型の要素(整数、文字、オブジェクトなど)に置き換えたものが、スタックというデータ構造です。 -下図に示すように、要素の山の上部を「スタックのトップ」、下部を「スタックのボトム」と呼びます。スタックのトップに要素を追加する操作を「プッシュ」、トップ要素を削除する操作を「ポップ」と呼びます。 +下図のように、積み重なった要素の上端を「スタックトップ」、下端を「スタックボトム」と呼びます。要素をスタックトップに追加する操作を「プッシュ」、スタックトップの要素を削除する操作を「ポップ」と呼びます。 -![スタックの後入先出ルール](stack.assets/stack_operations.png) +![スタックの後入れ先出しの規則](stack.assets/stack_operations.png) -## スタックの一般的な操作 +## スタックの基本操作 -スタックの一般的な操作を下表に示します。具体的なメソッド名は使用するプログラミング言語によって異なります。ここでは、例として`push()`、`pop()`、`peek()`を使用します。 +スタックの基本操作を以下の表に示します。具体的なメソッド名は使用するプログラミング言語によって異なります。ここでは、一般的な `push()`、`pop()`、`peek()` を例に挙げます。 -

  スタック操作の効率

+

  スタックの操作効率

-| メソッド | 説明 | 時間計算量 | -| -------- | ----------------------------------------------- | --------------- | -| `push()` | 要素をスタックにプッシュ(トップに追加) | $O(1)$ | -| `pop()` | スタックからトップ要素をポップ | $O(1)$ | -| `peek()` | スタックのトップ要素にアクセス | $O(1)$ | +| メソッド | 説明 | 時間計算量 | +| -------- | ---------------------- | ---------- | +| `push()` | 要素をプッシュする(スタックトップに追加) | $O(1)$ | +| `pop()` | スタックトップの要素をポップする | $O(1)$ | +| `peek()` | スタックトップの要素にアクセスする | $O(1)$ | -通常、プログラミング言語に組み込まれているスタッククラスを直接使用できます。ただし、一部の言語では具体的にスタッククラスを提供していない場合があります。これらの場合、言語の「配列」または「連結リスト」をスタックとして使用し、プログラムでスタックロジックに関連しない操作を無視できます。 +通常は、プログラミング言語に組み込まれているスタッククラスをそのまま利用できます。ただし、専用のスタッククラスが用意されていない言語もあります。その場合は、その言語の「配列」や「連結リスト」をスタックとして用い、プログラムのロジック上でスタックに無関係な操作を無視します。 === "Python" ```python title="stack.py" # スタックを初期化 - # Pythonには組み込みのスタッククラスがないため、listをスタックとして使用 + # Python には組み込みのスタッククラスがないため、list をスタックとして使用できる stack: list[int] = [] - # 要素をスタックにプッシュ + # 要素をプッシュ stack.append(1) stack.append(3) stack.append(2) stack.append(5) stack.append(4) - # スタックのトップ要素にアクセス + # スタックトップの要素にアクセス peek: int = stack[-1] - # スタックから要素をポップ + # 要素をポップ pop: int = stack.pop() # スタックの長さを取得 size: int = len(stack) - # スタックが空かどうかチェック + # 空かどうかを判定 is_empty: bool = len(stack) == 0 ``` @@ -55,23 +55,23 @@ /* スタックを初期化 */ stack stack; - /* 要素をスタックにプッシュ */ + /* 要素をプッシュ */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); - /* スタックのトップ要素にアクセス */ + /* スタックトップの要素にアクセス */ int top = stack.top(); - /* スタックから要素をポップ */ + /* 要素をポップ */ stack.pop(); // 戻り値なし /* スタックの長さを取得 */ int size = stack.size(); - /* スタックが空かどうかチェック */ + /* 空かどうかを判定 */ bool empty = stack.empty(); ``` @@ -81,23 +81,23 @@ /* スタックを初期化 */ Stack stack = new Stack<>(); - /* 要素をスタックにプッシュ */ + /* 要素をプッシュ */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); - /* スタックのトップ要素にアクセス */ + /* スタックトップの要素にアクセス */ int peek = stack.peek(); - /* スタックから要素をポップ */ + /* 要素をポップ */ int pop = stack.pop(); /* スタックの長さを取得 */ int size = stack.size(); - /* スタックが空かどうかチェック */ + /* 空かどうかを判定 */ boolean isEmpty = stack.isEmpty(); ``` @@ -107,23 +107,23 @@ /* スタックを初期化 */ Stack stack = new(); - /* 要素をスタックにプッシュ */ + /* 要素をプッシュ */ stack.Push(1); stack.Push(3); stack.Push(2); stack.Push(5); stack.Push(4); - /* スタックのトップ要素にアクセス */ + /* スタックトップの要素にアクセス */ int peek = stack.Peek(); - /* スタックから要素をポップ */ + /* 要素をポップ */ int pop = stack.Pop(); /* スタックの長さを取得 */ int size = stack.Count; - /* スタックが空かどうかチェック */ + /* 空かどうかを判定 */ bool isEmpty = stack.Count == 0; ``` @@ -131,27 +131,27 @@ ```go title="stack_test.go" /* スタックを初期化 */ - // Goでは、Sliceをスタックとして使用することが推奨されます + // Go では、Slice をスタックとして使うのが一般的 var stack []int - /* 要素をスタックにプッシュ */ + /* 要素をプッシュ */ stack = append(stack, 1) stack = append(stack, 3) stack = append(stack, 2) stack = append(stack, 5) stack = append(stack, 4) - /* スタックのトップ要素にアクセス */ + /* スタックトップの要素にアクセス */ peek := stack[len(stack)-1] - /* スタックから要素をポップ */ + /* 要素をポップ */ pop := stack[len(stack)-1] stack = stack[:len(stack)-1] /* スタックの長さを取得 */ size := len(stack) - /* スタックが空かどうかチェック */ + /* 空かどうかを判定 */ isEmpty := len(stack) == 0 ``` @@ -159,26 +159,26 @@ ```swift title="stack.swift" /* スタックを初期化 */ - // Swiftには組み込みのスタッククラスがないため、Arrayをスタックとして使用 + // Swift には組み込みのスタッククラスがないため、Array をスタックとして使用できる var stack: [Int] = [] - /* 要素をスタックにプッシュ */ + /* 要素をプッシュ */ stack.append(1) stack.append(3) stack.append(2) stack.append(5) stack.append(4) - /* スタックのトップ要素にアクセス */ + /* スタックトップの要素にアクセス */ let peek = stack.last! - /* スタックから要素をポップ */ + /* 要素をポップ */ let pop = stack.removeLast() /* スタックの長さを取得 */ let size = stack.count - /* スタックが空かどうかチェック */ + /* 空かどうかを判定 */ let isEmpty = stack.isEmpty ``` @@ -186,26 +186,26 @@ ```javascript title="stack.js" /* スタックを初期化 */ - // JavaScriptには組み込みのスタッククラスがないため、Arrayをスタックとして使用 + // JavaScript には組み込みのスタッククラスがないため、Array をスタックとして使用できる const stack = []; - /* 要素をスタックにプッシュ */ + /* 要素をプッシュ */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); - /* スタックのトップ要素にアクセス */ + /* スタックトップの要素にアクセス */ const peek = stack[stack.length-1]; - /* スタックから要素をポップ */ + /* 要素をポップ */ const pop = stack.pop(); /* スタックの長さを取得 */ const size = stack.length; - /* スタックが空かどうかチェック */ + /* 空かどうかを判定 */ const is_empty = stack.length === 0; ``` @@ -213,26 +213,26 @@ ```typescript title="stack.ts" /* スタックを初期化 */ - // TypeScriptには組み込みのスタッククラスがないため、Arrayをスタックとして使用 + // TypeScript には組み込みのスタッククラスがないため、Array をスタックとして使用できる const stack: number[] = []; - /* 要素をスタックにプッシュ */ + /* 要素をプッシュ */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); - /* スタックのトップ要素にアクセス */ + /* スタックトップの要素にアクセス */ const peek = stack[stack.length - 1]; - /* スタックから要素をポップ */ + /* 要素をポップ */ const pop = stack.pop(); /* スタックの長さを取得 */ const size = stack.length; - /* スタックが空かどうかチェック */ + /* 空かどうかを判定 */ const is_empty = stack.length === 0; ``` @@ -240,26 +240,26 @@ ```dart title="stack.dart" /* スタックを初期化 */ - // Dartには組み込みのスタッククラスがないため、Listをスタックとして使用 + // Dart には組み込みのスタッククラスがないため、List をスタックとして使用できる List stack = []; - /* 要素をスタックにプッシュ */ + /* 要素をプッシュ */ stack.add(1); stack.add(3); stack.add(2); stack.add(5); stack.add(4); - /* スタックのトップ要素にアクセス */ + /* スタックトップの要素にアクセス */ int peek = stack.last; - /* スタックから要素をポップ */ + /* 要素をポップ */ int pop = stack.removeLast(); /* スタックの長さを取得 */ int size = stack.length; - /* スタックが空かどうかチェック */ + /* 空かどうかを判定 */ bool isEmpty = stack.isEmpty; ``` @@ -267,55 +267,106 @@ ```rust title="stack.rs" /* スタックを初期化 */ - // Vecをスタックとして使用 + // Vec をスタックとして使用する let mut stack: Vec = Vec::new(); - /* 要素をスタックにプッシュ */ + /* 要素をプッシュ */ stack.push(1); stack.push(3); stack.push(2); stack.push(5); stack.push(4); - /* スタックのトップ要素にアクセス */ + /* スタックトップの要素にアクセス */ let top = stack.last().unwrap(); - /* スタックから要素をポップ */ + /* 要素をポップ */ let pop = stack.pop().unwrap(); /* スタックの長さを取得 */ let size = stack.len(); - /* スタックが空かどうかチェック */ + /* 空かどうかを判定 */ let is_empty = stack.is_empty(); ``` === "C" ```c title="stack.c" - // Cは組み込みのスタックを提供していません + // C には組み込みのスタックがない ``` === "Kotlin" ```kotlin title="stack.kt" + /* スタックを初期化 */ + val stack = Stack() + /* 要素をプッシュ */ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + stack.push(4) + + /* スタックトップの要素にアクセス */ + val peek = stack.peek() + + /* 要素をポップ */ + val pop = stack.pop() + + /* スタックの長さを取得 */ + val size = stack.size + + /* 空かどうかを判定 */ + val isEmpty = stack.isEmpty() ``` +=== "Ruby" + + ```ruby title="stack.rb" + # スタックを初期化 + # Ruby には組み込みのスタッククラスがないため、Array をスタックとして使用できる + stack = [] + + # 要素をプッシュ + stack << 1 + stack << 3 + stack << 2 + stack << 5 + stack << 4 + + # スタックトップの要素にアクセス + peek = stack.last + + # 要素をポップ + pop = stack.pop + + # スタックの長さを取得 + size = stack.length + + # 空かどうかを判定 + is_empty = stack.empty? + ``` + +??? pythontutor "実行の可視化" + + https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%88%0A%20%20%20%20%23%20Python%20%E6%B2%A1%E6%9C%89%E5%86%85%E7%BD%AE%E7%9A%84%E6%A0%88%E7%B1%BB%EF%BC%8C%E5%8F%AF%E4%BB%A5%E6%8A%8A%20list%20%E5%BD%93%E4%BD%9C%E6%A0%88%E6%9D%A5%E4%BD%BF%E7%94%A8%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E6%A0%88%0A%20%20%20%20stack.append%281%29%0A%20%20%20%20stack.append%283%29%0A%20%20%20%20stack.append%282%29%0A%20%20%20%20stack.append%285%29%0A%20%20%20%20stack.append%284%29%0A%20%20%20%20print%28%22%E6%A0%88%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack%5B-1%5D%0A%20%20%20%20print%28%22%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E6%A0%88%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%90%8E%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28stack%29%0A%20%20%20%20print%28%22%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28stack%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + ## スタックの実装 -スタックがどのように動作するかをより深く理解するために、自分でスタッククラスを実装してみましょう。 +スタックの動作の仕組みをより深く理解するために、自分でスタッククラスを実装してみましょう。 -スタックは後入先出の原則に従うため、スタックのトップでのみ要素を追加または削除できます。しかし、配列と連結リストの両方は任意の位置で要素を追加・削除できるため、**スタックは制限された配列または連結リストと見なすことができます**。言い換えれば、配列や連結リストの特定の無関係な操作を「遮蔽」して、外部の動作をスタックの特性に合わせることができます。 +スタックは後入れ先出しの原則に従うため、要素の追加や削除はスタックトップでしか行えません。一方、配列や連結リストでは任意の位置で要素を追加・削除できます。**つまり、スタックは制限付きの配列または連結リストとみなせます。** 言い換えると、配列や連結リストのうち無関係な操作を「隠蔽」することで、外から見た振る舞いをスタックの特性に合わせられます。 -### 連結リストベースの実装 +### 連結リストによる実装 -連結リストを使用してスタックを実装する場合、リストのヘッドノードをスタックのトップ、テールノードをスタックのボトムと考えることができます。 +連結リストでスタックを実装する場合、連結リストの先頭ノードをスタックトップ、末尾ノードをスタックボトムとみなせます。 -下図に示すように、プッシュ操作では、単に連結リストのヘッドに要素を挿入します。このノード挿入方法は「ヘッド挿入」として知られています。ポップ操作では、リストからヘッドノードを削除するだけです。 +下図のように、プッシュ操作では要素を連結リストの先頭に挿入するだけでよく、このノード挿入方法は「頭部挿入法」と呼ばれます。ポップ操作では、先頭ノードを連結リストから削除するだけです。 === "LinkedListStack" - ![連結リストによるスタック実装のプッシュとポップ操作](stack.assets/linkedlist_stack_step1.png) + ![連結リストによるスタック実装のプッシュ・ポップ操作](stack.assets/linkedlist_stack_step1.png) === "push()" ![linkedlist_stack_push](stack.assets/linkedlist_stack_step2_push.png) @@ -323,18 +374,18 @@ === "pop()" ![linkedlist_stack_pop](stack.assets/linkedlist_stack_step3_pop.png) -以下は、連結リストに基づくスタック実装のサンプルコードです: +以下は、連結リストによってスタックを実装したコード例です: ```src [file]{linkedlist_stack}-[class]{linked_list_stack}-[func]{} ``` -### 配列ベースの実装 +### 配列による実装 -配列を使用してスタックを実装する場合、配列の末尾をスタックのトップと考えることができます。下図に示すように、プッシュとポップ操作は、それぞれ配列の末尾での要素の追加と削除に対応し、どちらも時間計算量$O(1)$です。 +配列でスタックを実装する場合、配列の末尾をスタックトップとして扱えます。下図のように、プッシュとポップはそれぞれ配列末尾への要素追加と削除に対応し、どちらの時間計算量も $O(1)$ です。 === "ArrayStack" - ![配列によるスタック実装のプッシュとポップ操作](stack.assets/array_stack_step1.png) + ![配列によるスタック実装のプッシュ・ポップ操作](stack.assets/array_stack_step1.png) === "push()" ![array_stack_push](stack.assets/array_stack_step2_push.png) @@ -342,7 +393,7 @@ === "pop()" ![array_stack_pop](stack.assets/array_stack_step3_pop.png) -スタックにプッシュされる要素が継続的に増加する可能性があるため、動的配列を使用でき、配列拡張を自分で処理する必要がありません。以下はサンプルコードです: +プッシュされる要素は際限なく増える可能性があるため、動的配列を使えば、配列の拡張を自前で処理する必要がありません。以下にコード例を示します: ```src [file]{array_stack}-[class]{array_stack}-[func]{} @@ -350,30 +401,30 @@ ## 2つの実装の比較 -**サポートされる操作** +**対応する操作** -両方の実装は、スタックで定義されたすべての操作をサポートします。配列実装はさらにランダムアクセスをサポートしますが、これはスタック定義の範囲を超えており、一般的には使用されません。 +どちらの実装も、スタックの定義に含まれる各種操作をサポートします。配列ベースの実装はランダムアクセスも可能ですが、これはスタックの定義範囲を超えているため、通常は利用しません。 **時間効率** -配列ベースの実装では、プッシュとポップ操作の両方が事前に割り当てられた連続メモリで発生し、良好なキャッシュ局所性があるため効率が高くなります。しかし、プッシュ操作が配列容量を超える場合、リサイズメカニズムがトリガーされ、そのプッシュ操作の時間計算量は$O(n)$になります。 +配列ベースの実装では、プッシュとポップの両方があらかじめ確保された連続メモリ上で行われるため、キャッシュ局所性が高く、効率に優れます。ただし、プッシュ時に配列容量を超えると拡張処理が発生し、その1回のプッシュの時間計算量は $O(n)$ になります。 -連結リスト実装では、リスト拡張は非常に柔軟で、配列拡張のような効率低下の問題はありません。しかし、プッシュ操作にはノードオブジェクトの初期化とポインタの変更が必要なため、効率は比較的低くなります。プッシュされる要素がすでにノードオブジェクトの場合、初期化ステップをスキップでき、効率が向上します。 +連結リストベースの実装では、サイズ拡張が非常に柔軟であり、前述のような配列拡張による効率低下はありません。ただし、プッシュ時にはノードオブジェクトの初期化とポインタの更新が必要になるため、効率は相対的に低くなります。もっとも、プッシュする要素自体がノードオブジェクトであれば、初期化の手間を省けるため、効率を高められます。 -したがって、プッシュとポップ操作の要素が`int`や`double`などの基本データ型の場合、以下の結論を導くことができます: +以上を踏まえると、プッシュおよびポップの対象が `int` や `double` のような基本データ型である場合、次の結論が得られます。 -- 配列ベースのスタック実装は拡張時に効率が低下しますが、拡張は低頻度操作であるため、平均効率は高くなります。 -- 連結リストベースのスタック実装はより安定した効率パフォーマンスを提供します。 +- 配列ベースのスタックは拡張時に効率が低下しますが、拡張は低頻度の操作であるため、平均効率はより高くなります。 +- 連結リストベースのスタックは、より安定した効率を提供できます。 **空間効率** -リストを初期化する際、システムは「初期容量」を割り当てますが、これは実際の必要量を超える可能性があります。さらに、拡張メカニズムは通常、特定の係数(2倍など)で容量を増加させ、これも実際の必要量を超える可能性があります。したがって、**配列ベースのスタックは一部の空間を無駄にする可能性があります**。 +リストを初期化するとき、システムは「初期容量」を割り当てますが、この容量は実際の必要量を上回ることがあります。また、拡張は通常、一定の倍率(たとえば2倍)で行われるため、拡張後の容量も実際の必要量を超える可能性があります。したがって、**配列ベースのスタックは一定のメモリ浪費を招く可能性があります。** -しかし、連結リストノードはポインタを格納するための追加空間が必要なため、**連結リストノードが占有する空間は比較的大きくなります**。 +一方で、連結リストのノードはポインタを追加で保持する必要があるため、**連結リストノードは相対的に大きな領域を占有します。** -まとめると、どちらの実装がよりメモリ効率的かを単純に判断することはできません。特定の状況に基づく分析が必要です。 +以上より、どちらの実装がより省メモリかを単純に断定することはできず、具体的な状況に応じて分析する必要があります。 ## スタックの典型的な応用 -- **ブラウザの戻ると進む、ソフトウェアの元に戻すとやり直し**。新しいWebページを開くたびに、ブラウザは前のページをスタックにプッシュし、戻る操作(本質的にはポップ操作)を通じて前のページに戻ることができます。戻ると進むの両方をサポートするには、2つのスタックが連携して動作する必要があります。 -- **プログラムのメモリ管理**。関数が呼び出されるたびに、システムはスタックのトップにスタックフレームを追加して関数のコンテキスト情報を記録します。再帰関数では、下方向の再帰フェーズはスタックへのプッシュを続け、上方向のバックトラッキングフェーズはスタックからのポップを続けます。 +- **ブラウザにおける戻ると進む、ソフトウェアにおける取り消しとやり直し**。新しいWebページを開くたびに、ブラウザは直前のページをスタックにプッシュするため、戻る操作によって前のページに戻れます。戻る操作は実際にはポップに相当します。戻ると進むを同時にサポートするには、2つのスタックを組み合わせて実現する必要があります。 +- **プログラムのメモリ管理**。関数を呼び出すたびに、システムはスタックトップにスタックフレームを追加し、関数のコンテキスト情報を記録します。再帰関数では、下向きに再帰していく段階でプッシュが繰り返され、上向きにバックトラックする段階でポップが繰り返されます。 diff --git a/ja/docs/chapter_stack_and_queue/summary.md b/ja/docs/chapter_stack_and_queue/summary.md index 20e19c33a..7e60438b7 100644 --- a/ja/docs/chapter_stack_and_queue/summary.md +++ b/ja/docs/chapter_stack_and_queue/summary.md @@ -1,31 +1,31 @@ # まとめ -### 重要なポイント +### 要点の振り返り -- スタックは後入れ先出し(LIFO)の原則に従うデータ構造で、配列または連結リストを使って実装できます。 -- 時間効率の観点では、スタックの配列実装の方が平均的な効率が高いです。ただし、拡張時には単一のプッシュ操作の時間計算量が$O(n)$に悪化する可能性があります。対照的に、スタックの連結リスト実装はより安定した効率を提供します。 -- 空間効率に関しては、スタックの配列実装は一定程度の空間の無駄につながる可能性があります。ただし、連結リストのノードが占有するメモリ空間は一般的に配列の要素よりも大きいことに注意することが重要です。 -- キューは先入れ先出し(FIFO)の原則に従うデータ構造で、同様に配列または連結リストを使って実装できます。キューの時間と空間効率に関する結論は、スタックと似ています。 -- 両端キュー(deque)はより柔軟なキューの種類で、両端での要素の追加と削除を可能にします。 +- スタックは後入れ先出しの原則に従うデータ構造であり、配列または連結リストで実装できます。 +- 時間効率の面では、スタックの配列実装は平均効率が高い一方、拡張時には 1 回のプッシュ操作の時間計算量が $O(n)$ まで悪化します。これに対して、スタックの連結リスト実装はより安定した効率を示します。 +- 空間効率の面では、スタックの配列実装はある程度の領域の無駄を生む可能性があります。ただし、連結リストのノードが占有するメモリは配列要素よりも大きい点に注意が必要です。 +- キューは先入れ先出しの原則に従うデータ構造であり、同様に配列または連結リストで実装できます。時間効率と空間効率の比較における結論は、前述のスタックの場合と似ています。 +- 両端キューはより高い自由度を持つキューであり、両端で要素の追加と削除を行えます。 ### Q & A -**Q**: ブラウザの進む・戻る機能は双方向連結リストで実装されているのですか? +**Q**:ブラウザの進む・戻るは双方向連結リストで実装されているのですか? -ブラウザの進む・戻るナビゲーションは本質的に「スタック」概念の現れです。ユーザーが新しいページを訪問すると、そのページがスタックの先頭に追加されます。戻るボタンをクリックすると、ページがスタックの先頭からポップされます。両端キュー(deque)は、「両端キュー」の章で述べたように、いくつかの追加操作を便利に実装できます。 +ブラウザの進む・戻る機能の本質は「スタック」の表れです。ユーザーが新しいページにアクセスすると、そのページはスタックの先頭に追加されます。ユーザーが戻るボタンをクリックすると、そのページはスタックの先頭から取り出されます。両端キューを使うといくつかの追加操作を簡単に実装でき、この点は「両端キュー」の章で触れています。 -**Q**: スタックからポップした後、ポップされたノードのメモリを解放する必要がありますか? +**Q**:ポップした後、そのノードのメモリを解放する必要はありますか? -ポップされたノードが後で使用される場合は、そのメモリを解放する必要はありません。自動ガベージコレクションを持つJavaやPythonなどの言語では、手動のメモリ解放は必要ありません。CやC++では、手動のメモリ解放が必要です。 +後で取り出したノードを引き続き使うのであれば、メモリを解放する必要はありません。以降そのノードを使わない場合でも、`Java` や `Python` などの言語には自動ガベージコレクション機構があるため、手動でメモリを解放する必要はありません。一方、`C` と `C++` では手動でメモリを解放する必要があります。 -**Q**: 両端キューは2つのスタックを結合したもののように見えます。その用途は何ですか? +**Q**:両端キューは 2 つのスタックをつなげたように見えますが、用途は何ですか? -両端キューは、スタックとキューの組み合わせまたは2つのスタックを結合したもので、スタックとキューの両方のロジックを示します。したがって、スタックとキューのすべてのアプリケーションを実装でき、より大きな柔軟性を提供します。 +両端キューは、スタックとキューの組み合わせ、あるいは 2 つのスタックをつなげたもののような構造です。表しているのはスタック + キューのロジックなので、スタックとキューのすべての応用を実現でき、しかもより柔軟です。 -**Q**: 元に戻すとやり直しは具体的にどのように実装されるのですか? +**Q**:取り消し(undo)とやり直し(redo)は具体的にどのように実装されますか? -元に戻すとやり直しの操作は2つのスタックを使って実装されます:元に戻す用のスタック`A`とやり直し用のスタック`B`です。 +2 つのスタックを使い、スタック `A` を取り消し用、スタック `B` をやり直し用に使います。 -1. ユーザーが操作を実行するたびに、それがスタック`A`にプッシュされ、スタック`B`がクリアされます。 -2. ユーザーが「元に戻す」を実行すると、最新の操作がスタック`A`からポップされ、スタック`B`にプッシュされます。 -3. ユーザーが「やり直し」を実行すると、最新の操作がスタック`B`からポップされ、スタック`A`に戻されます。 +1. ユーザーが操作を 1 つ実行するたびに、その操作をスタック `A` にプッシュし、スタック `B` を空にします。 +2. ユーザーが「取り消し」を実行したときは、スタック `A` から直近の操作をポップし、それをスタック `B` にプッシュします。 +3. ユーザーが「やり直し」を実行したときは、スタック `B` から直近の操作をポップし、それをスタック `A` にプッシュします。 diff --git a/ja/docs/chapter_tree/array_representation_of_tree.md b/ja/docs/chapter_tree/array_representation_of_tree.md index b8e834709..62466bedf 100644 --- a/ja/docs/chapter_tree/array_representation_of_tree.md +++ b/ja/docs/chapter_tree/array_representation_of_tree.md @@ -1,34 +1,34 @@ # 二分木の配列表現 -連結リスト表現では、二分木の格納単位はノード`TreeNode`であり、ノードはポインタによって接続されます。連結リスト表現での二分木の基本操作については前の節で紹介しました。 +連結リスト表現では、二分木の記憶単位はノード `TreeNode` であり、ノード同士はポインタによって接続されます。前節では、連結リスト表現における二分木の各種基本操作を紹介しました。 -では、配列を使って二分木を表現することはできるでしょうか?答えはイエスです。 +では、配列で二分木を表現できるでしょうか?答えはもちろん可能です。 -## 完全二分木の表現 +## 充足二分木を表現する -まず簡単なケースから分析してみましょう。完全二分木が与えられたとき、レベル順探索の順序に従ってすべてのノードを配列に格納し、各ノードは一意の配列インデックスに対応します。 +まずは簡単な例を考えます。与えられた 1 本の充足二分木について、すべてのノードをレベル順走査の順に配列へ格納すると、各ノードは一意な配列インデックスに対応します。 -レベル順探索の特性に基づいて、親ノードのインデックスとその子ノードの間の「マッピング公式」を導き出すことができます:**ノードのインデックスが$i$の場合、その左の子のインデックスは$2i + 1$、右の子のインデックスは$2i + 2$です**。下図は、さまざまなノードのインデックス間のマッピング関係を示しています。 +レベル順走査の性質に基づくと、親ノードのインデックスと子ノードのインデックスの間にある「対応式」を導けます。**あるノードのインデックスが $i$ なら、その左子ノードのインデックスは $2i + 1$ 、右子ノードのインデックスは $2i + 2$ です**。以下の図は、各ノードインデックス間の対応関係を示しています。 -![完全二分木の配列表現](array_representation_of_tree.assets/array_representation_binary_tree.png) +![充足二分木の配列表現](array_representation_of_tree.assets/array_representation_binary_tree.png) -**マッピング公式は、連結リストのノード参照(ポインタ)と同様の役割を果たします**。配列内の任意のノードが与えられたとき、マッピング公式を使用してその左(右)の子ノードにアクセスできます。 +**対応式は、連結リストにおけるノード参照(ポインタ)と同じ役割を果たします**。与えられた配列内の任意のノードについて、この対応式を使えばその左(右)子ノードにアクセスできます。 -## 任意の二分木の表現 +## 任意の二分木を表現する -完全二分木は特別なケースです。二分木の中間レベルには多くの`None`値が存在することがよくあります。レベル順探索のシーケンスにはこれらの`None`値が含まれないため、このシーケンスだけに依存して`None`値の数と分布を推測することはできません。**つまり、複数の二分木構造が同じレベル順探索シーケンスと一致する可能性があります**。 +充足二分木は特殊なケースであり、一般の二分木では中間層に多数の `None` が存在することがよくあります。レベル順走査の列にはこれらの `None` が含まれないため、その列だけから `None` の数や分布位置を推定することはできません。**つまり、このレベル順走査列に一致する二分木構造は複数存在し得ます**。 -下図に示すように、完全でない二分木が与えられた場合、上記の配列表現方法は失敗します。 +次の図のように、非充足二分木が与えられると、上記の配列表現はすでに成り立ちません。 -![レベル順探索シーケンスが複数の二分木の可能性に対応](array_representation_of_tree.assets/array_representation_without_empty.png) +![レベル順走査列に対応する複数の二分木の可能性](array_representation_of_tree.assets/array_representation_without_empty.png) -この問題を解決するために、**レベル順探索シーケンスですべての`None`値を明示的に書き出すことを検討できます**。下図に示すように、この処理後、レベル順探索シーケンスは二分木を一意に表現できます。サンプルコードは以下の通りです: +この問題を解決するために、**レベル順走査列にすべての `None` を明示的に書き込む**ことを考えられます。次の図のように、このように処理すればレベル順走査列で二分木を一意に表現できます。コード例は以下のとおりです: === "Python" ```python title="" # 二分木の配列表現 - # Noneを使用して空のスロットを表現 + # 空き位置を表すために None を使う tree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] ``` @@ -36,7 +36,7 @@ ```cpp title="" /* 二分木の配列表現 */ - // 最大整数値INT_MAXを使用して空のスロットをマーク + // int の最大値 INT_MAX を使って空き位置を示す vector tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; ``` @@ -44,7 +44,7 @@ ```java title="" /* 二分木の配列表現 */ - // Integerラッパークラスを使用してnullで空のスロットをマーク + // int のラッパークラス Integer を使えば、null で空き位置を示せる Integer[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; ``` @@ -52,7 +52,7 @@ ```csharp title="" /* 二分木の配列表現 */ - // nullable int (int?)を使用してnullで空のスロットをマーク + // nullable な int? 型を使えば、null で空き位置を示せる int?[] tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` @@ -60,7 +60,7 @@ ```go title="" /* 二分木の配列表現 */ - // any型スライスを使用してnilで空のスロットをマーク + // any 型のスライスを使えば、nil で空き位置を示せる tree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15} ``` @@ -68,7 +68,7 @@ ```swift title="" /* 二分木の配列表現 */ - // optional Int (Int?)を使用してnilで空のスロットをマーク + // nullable な Int? 型を使えば、nil で空き位置を示せる let tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] ``` @@ -76,7 +76,7 @@ ```javascript title="" /* 二分木の配列表現 */ - // nullを使用して空のスロットを表現 + // null を使って空き位置を表す let tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` @@ -84,7 +84,7 @@ ```typescript title="" /* 二分木の配列表現 */ - // nullを使用して空のスロットを表現 + // null を使って空き位置を表す let tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` @@ -92,7 +92,7 @@ ```dart title="" /* 二分木の配列表現 */ - // nullable int (int?)を使用してnullで空のスロットをマーク + // nullable な int? 型を使えば、null で空き位置を示せる List tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15]; ``` @@ -100,7 +100,7 @@ ```rust title="" /* 二分木の配列表現 */ - // Noneを使用して空のスロットをマーク + // None を使って空き位置を示す let tree = [Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15)]; ``` @@ -108,7 +108,7 @@ ```c title="" /* 二分木の配列表現 */ - // 最大int値を使用して空のスロットをマーク、したがってノード値はINT_MAXであってはならない + // int の最大値で空き位置を示すため、ノード値は INT_MAX であってはならない int tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15}; ``` @@ -116,43 +116,45 @@ ```kotlin title="" /* 二分木の配列表現 */ - // nullを使用して空のスロットを表現 - val tree = mutableListOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ) + // null を使って空き位置を表す + val tree = arrayOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 ) ``` === "Ruby" ```ruby title="" - + ### 二分木の配列表現 ### + # nil を使って空き位置を表す + tree = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15] ``` -![任意の種類の二分木の配列表現](array_representation_of_tree.assets/array_representation_with_empty.png) +![任意の二分木の配列表現](array_representation_of_tree.assets/array_representation_with_empty.png) -注目すべきは、**完備二分木は配列表現に非常に適している**ということです。完備二分木の定義を思い出すと、`None`は最下位レベルでのみ、かつ右側に向かって現れます。**つまり、すべての`None`値は確実にレベル順探索シーケンスの最後に現れます**。 +補足すると、**完全二分木は配列による表現に非常に適しています**。完全二分木の定義を振り返ると、`None` は最下層の右側にしか現れないため、**すべての `None` は必ずレベル順走査列の末尾に現れます**。 -これは、配列を使用して完備二分木を表現する際、すべての`None`値の格納を省略できることを意味し、非常に便利です。下図に例を示します。 +つまり、完全二分木を配列で表す場合は、すべての `None` の格納を省略できるため、非常に便利です。次の図に例を示します。 -![完備二分木の配列表現](array_representation_of_tree.assets/array_representation_complete_binary_tree.png) +![完全二分木の配列表現](array_representation_of_tree.assets/array_representation_complete_binary_tree.png) -以下のコードは、配列表現に基づく二分木を実装し、次の操作を含みます: +以下のコードでは、配列ベースで表現した二分木を実装しており、次の操作を含みます。 -- ノードが与えられたとき、その値、左(右)の子ノード、および親ノードを取得する。 -- 前順、中順、後順、およびレベル順探索シーケンスを取得する。 +- あるノードが与えられたとき、その値、左(右)子ノード、親ノードを取得する。 +- 前順走査、中順走査、後順走査、レベル順走査の列を取得する。 ```src [file]{array_binary_tree}-[class]{array_binary_tree}-[func]{} ``` -## 利点と制限 +## 利点と制約 -二分木の配列表現には以下の利点があります: +二分木の配列表現には主に次の利点があります。 -- 配列は連続したメモリ空間に格納されるため、キャッシュフレンドリーで、より高速なアクセスと探索が可能です。 -- ポインタを格納する必要がないため、スペースを節約できます。 -- ノードへのランダムアクセスが可能です。 +- 配列は連続したメモリ空間に格納されるため、キャッシュ効率が高く、アクセスと走査が速い。 +- ポインタを格納する必要がなく、比較的省スペースである。 +- ノードへのランダムアクセスが可能である。 -しかし、配列表現にはいくつかの制限もあります: +ただし、配列表現にはいくつかの制約もあります。 -- 配列格納には連続したメモリ空間が必要なため、大量のデータを持つ木の格納には適していません。 -- ノードの追加や削除には配列の挿入や削除操作が必要で、効率が低くなります。 -- 二分木に多くの`None`値がある場合、配列に含まれるノードデータの割合が低くなり、空間利用率が低下します。 +- 配列による格納には連続したメモリ空間が必要なため、データ量が大きすぎる木の格納には向かない。 +- ノードの追加と削除は配列の挿入・削除操作で実現する必要があり、効率は低い。 +- 二分木に大量の `None` が存在すると、配列に占める実ノードデータの比率が低くなり、空間利用率も低下する。 diff --git a/ja/docs/chapter_tree/avl_tree.md b/ja/docs/chapter_tree/avl_tree.md index 5ea79187f..f7763693f 100644 --- a/ja/docs/chapter_tree/avl_tree.md +++ b/ja/docs/chapter_tree/avl_tree.md @@ -1,46 +1,46 @@ -# AVL木 * +# AVL 木 * -「二分探索木」の節では、複数の挿入と削除の後、二分探索木が連結リストに退化する可能性があることを述べました。このような場合、すべての操作の時間計算量が$O(\log n)$から$O(n)$に悪化します。 +「二分探索木」章で述べたように、挿入と削除を何度も繰り返すと、二分探索木は連結リストへ退化する可能性があります。この場合、すべての操作の時間計算量は $O(\log n)$ から $O(n)$ へ劣化します。 -下図に示すように、2つのノード削除操作の後、この二分探索木は連結リストに退化します。 +以下の図に示すように、ノード削除を 2 回行うと、この二分探索木は連結リストへ退化します。 -![ノード削除後のAVL木の退化](avl_tree.assets/avltree_degradation_from_removing_node.png) +![AVL 木がノード削除後に退化する](avl_tree.assets/avltree_degradation_from_removing_node.png) -例えば、下図に示す完全二分木では、2つのノードを挿入した後、木が左に大きく傾き、検索操作の時間計算量も悪化します。 +別の例として、以下の図に示す完全二分木に 2 つのノードを挿入すると、木は大きく左に傾き、探索操作の時間計算量もそれに伴って劣化します。 -![ノード挿入後のAVL木の退化](avl_tree.assets/avltree_degradation_from_inserting_node.png) +![AVL 木がノード挿入後に退化する](avl_tree.assets/avltree_degradation_from_inserting_node.png) -1962年、G. M. Adelson-VelskyとE. M. Landisが論文「An algorithm for the organization of information」でAVL木を提案しました。この論文では、ノードの継続的な追加と削除の後もAVL木が退化しないことを保証する一連の操作について詳述し、さまざまな操作の時間計算量を$O(\log n)$レベルに維持しました。つまり、頻繁な追加、削除、検索、変更が必要なシナリオで、AVL木は常に効率的なデータ操作性能を維持でき、大きな応用価値があります。 +1962 年、G. M. Adelson-Velsky と E. M. Landis は論文“An algorithm for the organization of information”の中で AVL 木 を提案しました。論文では一連の操作が詳しく説明されており、ノードの追加と削除を続けても AVL 木が退化しないようにして、各種操作の時間計算量を $O(\log n)$ の水準に保ちます。言い換えると、追加・削除・探索・更新を頻繁に行う場面でも、AVL 木は常に高いデータ操作性能を維持でき、実用価値の高い構造です。 -## AVL木の一般的な用語 +## AVL 木の基本用語 -AVL木は二分探索木でありかつ平衡二分木でもあり、これら2つの種類の二分木のすべての性質を満たしているため、平衡二分探索木です。 +AVL 木は二分探索木であると同時に平衡二分木でもあり、これら 2 種類の二分木の性質をすべて満たします。したがって、平衡二分探索木(balanced binary search tree)の一種です。 ### ノードの高さ -AVL木に関連する操作ではノードの高さを取得する必要があるため、ノードクラスに`height`変数を追加する必要があります: +AVL 木の操作ではノードの高さを取得する必要があるため、ノードクラスに `height` 変数を追加します: === "Python" ```python title="" class TreeNode: - """AVL木ノード""" + """AVL 木ノードクラス""" def __init__(self, val: int): self.val: int = val # ノード値 self.height: int = 0 # ノードの高さ - self.left: TreeNode | None = None # 左の子への参照 - self.right: TreeNode | None = None # 右の子への参照 + self.left: TreeNode | None = None # 左の子ノード参照 + self.right: TreeNode | None = None # 右の子ノード参照 ``` === "C++" ```cpp title="" - /* AVL木ノード */ + /* AVL 木ノードクラス */ struct TreeNode { int val{}; // ノード値 int height = 0; // ノードの高さ - TreeNode *left{}; // 左の子 - TreeNode *right{}; // 右の子 + TreeNode *left{}; // 左の子ノード + TreeNode *right{}; // 右の子ノード TreeNode() = default; explicit TreeNode(int x) : val(x){} }; @@ -49,12 +49,12 @@ AVL木に関連する操作ではノードの高さを取得する必要があ === "Java" ```java title="" - /* AVL木ノード */ + /* AVL 木ノードクラス */ class TreeNode { public int val; // ノード値 public int height; // ノードの高さ - public TreeNode left; // 左の子 - public TreeNode right; // 右の子 + public TreeNode left; // 左の子ノード + public TreeNode right; // 右の子ノード public TreeNode(int x) { val = x; } } ``` @@ -62,36 +62,36 @@ AVL木に関連する操作ではノードの高さを取得する必要があ === "C#" ```csharp title="" - /* AVL木ノード */ + /* AVL 木ノードクラス */ class TreeNode(int? x) { public int? val = x; // ノード値 public int height; // ノードの高さ - public TreeNode? left; // 左の子への参照 - public TreeNode? right; // 右の子への参照 + public TreeNode? left; // 左の子ノード参照 + public TreeNode? right; // 右の子ノード参照 } ``` === "Go" ```go title="" - /* AVL木ノード */ + /* AVL 木ノード構造体 */ type TreeNode struct { Val int // ノード値 Height int // ノードの高さ - Left *TreeNode // 左の子への参照 - Right *TreeNode // 右の子への参照 + Left *TreeNode // 左の子ノード参照 + Right *TreeNode // 右の子ノード参照 } ``` === "Swift" ```swift title="" - /* AVL木ノード */ + /* AVL 木ノードクラス */ class TreeNode { var val: Int // ノード値 var height: Int // ノードの高さ - var left: TreeNode? // 左の子 - var right: TreeNode? // 右の子 + var left: TreeNode? // 左の子ノード + var right: TreeNode? // 右の子ノード init(x: Int) { val = x @@ -103,12 +103,12 @@ AVL木に関連する操作ではノードの高さを取得する必要があ === "JS" ```javascript title="" - /* AVL木ノード */ + /* AVL 木ノードクラス */ class TreeNode { val; // ノード値 - height; // ノードの高さ - left; // 左の子ポインタ - right; // 右の子ポインタ + height; //ノードの高さ + left; // 左の子ノードポインタ + right; // 右の子ノードポインタ constructor(val, left, right, height) { this.val = val === undefined ? 0 : val; this.height = height === undefined ? 0 : height; @@ -121,12 +121,12 @@ AVL木に関連する操作ではノードの高さを取得する必要があ === "TS" ```typescript title="" - /* AVL木ノード */ + /* AVL 木ノードクラス */ class TreeNode { val: number; // ノード値 height: number; // ノードの高さ - left: TreeNode | null; // 左の子ポインタ - right: TreeNode | null; // 右の子ポインタ + left: TreeNode | null; // 左の子ノードポインタ + right: TreeNode | null; // 右の子ノードポインタ constructor(val?: number, height?: number, left?: TreeNode | null, right?: TreeNode | null) { this.val = val === undefined ? 0 : val; this.height = height === undefined ? 0 : height; @@ -139,12 +139,12 @@ AVL木に関連する操作ではノードの高さを取得する必要があ === "Dart" ```dart title="" - /* AVL木ノード */ + /* AVL 木ノードクラス */ class TreeNode { int val; // ノード値 int height; // ノードの高さ - TreeNode? left; // 左の子 - TreeNode? right; // 右の子 + TreeNode? left; // 左の子ノード + TreeNode? right; // 右の子ノード TreeNode(this.val, [this.height = 0, this.left, this.right]); } ``` @@ -155,12 +155,12 @@ AVL木に関連する操作ではノードの高さを取得する必要があ use std::rc::Rc; use std::cell::RefCell; - /* AVL木ノード */ + /* AVL 木ノード構造体 */ struct TreeNode { val: i32, // ノード値 height: i32, // ノードの高さ - left: Option>>, // 左の子 - right: Option>>, // 右の子 + left: Option>>, // 左の子ノード + right: Option>>, // 右の子ノード } impl TreeNode { @@ -179,8 +179,8 @@ AVL木に関連する操作ではノードの高さを取得する必要があ === "C" ```c title="" - /* AVL木ノード */ - TreeNode struct TreeNode { + /* AVL 木ノード構造体 */ + typedef struct TreeNode { int val; int height; struct TreeNode *left; @@ -203,29 +203,40 @@ AVL木に関連する操作ではノードの高さを取得する必要があ === "Kotlin" ```kotlin title="" - /* AVL木ノード */ + /* AVL 木ノードクラス */ class TreeNode(val _val: Int) { // ノード値 val height: Int = 0 // ノードの高さ - val left: TreeNode? = null // 左の子 - val right: TreeNode? = null // 右の子 + val left: TreeNode? = null // 左の子ノード + val right: TreeNode? = null // 右の子ノード } ``` === "Ruby" ```ruby title="" + ### AVL 木ノードクラス ### + class TreeNode + attr_accessor :val # ノード値 + attr_accessor :height # ノードの高さ + attr_accessor :left # 左の子ノード参照 + attr_accessor :right # 右の子ノード参照 + def initialize(val) + @val = val + @height = 0 + end + end ``` -「ノードの高さ」とは、そのノードから最も遠い葉ノードまでの距離、つまり通過する「辺」の数を指します。重要なのは、葉ノードの高さは$0$で、nullノードの高さは$-1$であることです。ノードの高さを取得し、更新するための2つのユーティリティ関数を作成します: +「ノードの高さ」とは、そのノードから最も遠い葉ノードまでの距離、すなわち通過する「辺」の本数を指します。特に、葉ノードの高さは $0$、空ノードの高さは $-1$ です。ここでは、ノードの高さを取得・更新するための 2 つの補助関数を用意します: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{update_height} ``` -### ノードの平衡因子 +### ノードの平衡係数 -ノードの平衡因子は、そのノードの左部分木の高さから右部分木の高さを引いた値として定義され、nullノードの平衡因子は$0$として定義されます。後で使いやすくするため、ノードの平衡因子を取得する機能も関数にカプセル化します: +ノードの平衡係数(balance factor)は、左部分木の高さから右部分木の高さを引いた値と定義し、空ノードの平衡係数は $0$ とします。同様に、ノードの平衡係数を取得する機能も関数にカプセル化して、後続で使いやすくします: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{balance_factor} @@ -233,17 +244,17 @@ AVL木に関連する操作ではノードの高さを取得する必要があ !!! tip - 平衡因子を$f$とすると、AVL木の任意のノードの平衡因子は$-1 \le f \le 1$を満たします。 + 平衡係数を $f$ とすると、AVL 木の任意のノードの平衡係数は常に $-1 \le f \le 1$ を満たします。 -## AVL木の回転 +## AVL 木の回転 -AVL木の特徴的な機能は「回転」操作で、これは二分木の中順探索シーケンスに影響を与えることなく、不平衡なノードのバランスを回復できます。つまり、**回転操作は「二分探索木」の性質を維持しながら、木を「平衡二分木」に戻すことができます**。 +AVL 木の特徴は「回転」操作にあり、二分木の中順走査列を変えずに、不平衡ノードを再び平衡に戻せます。言い換えると、**回転操作は「二分探索木」の性質を保ちながら、木を再び「平衡二分木」に戻すことができます**。 -絶対平衡因子が$> 1$のノードを「不平衡ノード」と呼びます。不平衡のタイプに応じて、4種類の回転があります:右回転、左回転、右左回転、左右回転です。以下、これらの回転操作について詳しく説明します。 +平衡係数の絶対値が $> 1$ のノードを「不平衡ノード」と呼びます。ノードの不平衡の形に応じて、回転操作は 4 種類に分かれます。右回転、左回転、右回転してから左回転、左回転してから右回転です。以下でこれらを順に説明します。 ### 右回転 -下図に示すように、二分木で下から上への最初の不平衡ノードは「ノード3」です。この不平衡ノードを根とする部分木に焦点を当て、これを`node`とし、その左の子を`child`として、「右回転」を実行します。右回転後、部分木は再びバランスが取れ、同時に二分探索木の性質も維持されます。 +以下の図では、ノードの下に平衡係数を示しています。下から上へ見ると、二分木で最初に不平衡になるのは「ノード 3」です。この不平衡ノードを根とする部分木に注目し、そのノードを `node`、左の子ノードを `child` として、「右回転」を行います。右回転後、部分木は平衡を回復し、なおかつ二分探索木の性質も保たれます。 === "<1>" ![右回転の手順](avl_tree.assets/avltree_right_rotate_step1.png) @@ -257,11 +268,11 @@ AVL木の特徴的な機能は「回転」操作で、これは二分木の中 === "<4>" ![avltree_right_rotate_step4](avl_tree.assets/avltree_right_rotate_step4.png) -下図に示すように、`child`ノードに右の子(`grand_child`と表記)がある場合、右回転で手順を追加する必要があります:`grand_child`を`node`の左の子に設定します。 +以下の図に示すように、ノード `child` に右の子ノード(`grand_child` と記す)がある場合、右回転には 1 ステップ追加する必要があります。すなわち、`grand_child` を `node` の左の子ノードにします。 -![grand_childがある右回転](avl_tree.assets/avltree_right_rotate_with_grandchild.png) +![grand_child を持つ右回転](avl_tree.assets/avltree_right_rotate_with_grandchild.png) -「右回転」は比喩的な用語で、実際にはノードポインタを変更することで実現されます。以下のコードで示されます: +「右に回転する」というのはあくまでイメージしやすい表現であり、実際にはノードポインタを変更して実現します。コードは次のとおりです: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{right_rotate} @@ -269,60 +280,60 @@ AVL木の特徴的な機能は「回転」操作で、これは二分木の中 ### 左回転 -対応して、上記の不平衡二分木の「鏡像」を考慮すると、下図に示す「左回転」操作を実行する必要があります。 +対応する鏡像として、上記の不平衡二分木を左右反転して考えると、以下の図に示す「左回転」が必要になります。 -![左回転操作](avl_tree.assets/avltree_left_rotate.png) +![左回転](avl_tree.assets/avltree_left_rotate.png) -同様に、下図に示すように、`child`ノードに左の子(`grand_child`と表記)がある場合、左回転で手順を追加する必要があります:`grand_child`を`node`の右の子に設定します。 +同様に、以下の図に示すように、ノード `child` に左の子ノード(`grand_child` と記す)がある場合、左回転にも 1 ステップ追加する必要があります。すなわち、`grand_child` を `node` の右の子ノードにします。 -![grand_childがある左回転](avl_tree.assets/avltree_left_rotate_with_grandchild.png) +![grand_child を持つ左回転](avl_tree.assets/avltree_left_rotate_with_grandchild.png) -**右回転と左回転の操作は論理的に対称であり、2つの対称的な不平衡タイプを解決します**ことが観察できます。対称性に基づいて、右回転の実装コードですべての`left`を`right`に、すべての`right`を`left`に置き換えることで、左回転の実装コードを得ることができます: +分かるように、**右回転と左回転は論理的に鏡像対称であり、それぞれが解決する 2 種類の不平衡も対称です**。この対称性に基づけば、右回転の実装コードにあるすべての `left` を `right` に、すべての `right` を `left` に置き換えるだけで、左回転の実装コードが得られます: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{left_rotate} ``` -### 左右回転 +### 左回転してから右回転 -下図に示す不平衡ノード3の場合、左回転または右回転のいずれかだけでは部分木のバランスを回復できません。この場合、まず`child`に対して「左回転」を実行し、次に`node`に対して「右回転」を実行する必要があります。 +以下の図の不平衡ノード 3 では、左回転だけでも右回転だけでも部分木を平衡に戻せません。この場合は、まず `child` に「左回転」を行い、次に `node` に「右回転」を行います。 -![左右回転](avl_tree.assets/avltree_left_right_rotate.png) +![左回転してから右回転](avl_tree.assets/avltree_left_right_rotate.png) -### 右左回転 +### 右回転してから左回転 -下図に示すように、上記の不平衡二分木の鏡像ケースでは、まず`child`に対して「右回転」を実行し、次に`node`に対して「左回転」を実行する必要があります。 +以下の図に示すように、上記の不平衡二分木の鏡像のケースでは、まず `child` に「右回転」を行い、次に `node` に「左回転」を行います。 -![右左回転](avl_tree.assets/avltree_right_left_rotate.png) +![右回転してから左回転](avl_tree.assets/avltree_right_left_rotate.png) ### 回転の選択 -下図に示す4種類の不平衡は、それぞれ上記で説明したケースに対応し、右回転、左右回転、右左回転、左回転が必要です。 +以下の図に示す 4 種類の不平衡は、上の各ケースにそれぞれ対応しており、必要な操作は順に右回転、左回転してから右回転、右回転してから左回転、左回転です。 -![AVL木の4つの回転ケース](avl_tree.assets/avltree_rotation_cases.png) +![AVL 木の 4 つの回転ケース](avl_tree.assets/avltree_rotation_cases.png) -下表に示すように、不平衡ノードの平衡因子とその高い側の子の平衡因子の符号を判断することで、不平衡ノードが上記のどのケースに属するかを決定します。 +以下の表に示すように、不平衡ノードの平衡係数と、高い側の子ノードの平衡係数の符号を判定することで、その不平衡ノードが上図のどのケースに属するかを判断できます。 -

  4つの回転ケースの選択条件

+

  4 種類の回転ケースの選択条件

-| 不平衡ノードの平衡因子 | 子ノードの平衡因子 | 使用する回転方法 | -| --------------------- | ----------------- | --------------------------- | -| $> 1$(左に傾いた木) | $\geq 0$ | 右回転 | -| $> 1$(左に傾いた木) | $<0$ | 左回転してから右回転 | -| $< -1$(右に傾いた木) | $\leq 0$ | 左回転 | -| $< -1$(右に傾いた木) | $>0$ | 右回転してから左回転 | +| 不平衡ノードの平衡係数 | 子ノードの平衡係数 | 採用すべき回転方法 | +| ------------------ | ---------------- | ---------------- | +| $> 1$ (左に偏った木) | $\geq 0$ | 右回転 | +| $> 1$ (左に偏った木) | $<0$ | 左回転してから右回転 | +| $< -1$ (右に偏った木) | $\leq 0$ | 左回転 | +| $< -1$ (右に偏った木) | $>0$ | 右回転してから左回転 | -便宜上、回転操作を関数にカプセル化します。**この関数により、さまざまな種類の不平衡に対して回転を実行し、不平衡ノードのバランスを回復できます**。コードは以下の通りです: +使いやすくするために、回転操作を 1 つの関数にカプセル化します。**この関数があれば、さまざまな不平衡ケースに対して回転を行い、不平衡ノードを再び平衡に戻せます**。コードは次のとおりです: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{rotate} ``` -## AVL木の一般的な操作 +## AVL 木の基本操作 ### ノードの挿入 -AVL木のノード挿入操作は二分探索木のそれと似ています。唯一の違いは、AVL木でノードを挿入した後、そのノードから根ノードまでのパス上に一連の不平衡ノードが現れる可能性があることです。したがって、**このノードから始めて上向きに回転操作を実行し、すべての不平衡ノードのバランスを回復する必要があります**。コードは以下の通りです: +AVL 木のノード挿入は、基本的には二分探索木と同じです。唯一の違いは、AVL 木ではノード挿入後に、そのノードから根ノードまでの経路上に複数の不平衡ノードが現れる可能性があることです。したがって、**このノードから開始して、下から上へ回転操作を行い、すべての不平衡ノードを平衡に戻す必要があります**。コードは次のとおりです: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{insert_helper} @@ -330,18 +341,18 @@ AVL木のノード挿入操作は二分探索木のそれと似ています。 ### ノードの削除 -同様に、二分探索木でのノード削除方法に基づいて、下から上へ回転操作を実行してすべての不平衡ノードのバランスを回復する必要があります。コードは以下の通りです: +同様に、二分探索木のノード削除メソッドを土台として、下から上へ回転操作を行い、すべての不平衡ノードを平衡に戻す必要があります。コードは次のとおりです: ```src [file]{avl_tree}-[class]{avl_tree}-[func]{remove_helper} ``` -### ノードの検索 +### ノードの探索 -AVL木でのノード検索操作は二分探索木のそれと一致しており、ここでは詳述しません。 +AVL 木のノード探索操作は二分探索木と同じなので、ここでは繰り返しません。 -## AVL木の典型的な応用 +## AVL 木の代表的な応用 -- 大量のデータの整理と格納に使用され、検索頻度が高く、挿入と削除の頻度が低いシナリオに適しています。 -- データベースのインデックスシステムの構築に使用されます。 -- 赤黒木も一般的な平衡二分探索木の一種です。AVL木と比較して、赤黒木はより緩い平衡条件を持ち、ノードの挿入と削除にかかる回転数が少なく、ノードの追加と削除操作の平均効率が高くなります。 +- 大規模データの整理・格納に用いられ、高頻度の探索と低頻度の追加・削除に適しています。 +- データベースのインデックスシステムの構築に使われます。 +- 赤黒木も代表的な平衡二分探索木の一つです。AVL 木と比べると、赤黒木は平衡条件がより緩く、ノードの挿入・削除に必要な回転操作が少ないため、平均的な更新効率はより高くなります。 diff --git a/ja/docs/chapter_tree/binary_search_tree.md b/ja/docs/chapter_tree/binary_search_tree.md index 2b54466f4..41557bdd6 100644 --- a/ja/docs/chapter_tree/binary_search_tree.md +++ b/ja/docs/chapter_tree/binary_search_tree.md @@ -1,26 +1,26 @@ # 二分探索木 -下図に示すように、二分探索木は以下の条件を満たします。 +以下の図に示すように、二分探索木(binary search tree)は次の条件を満たします。 1. 根ノードについて、左部分木のすべてのノードの値 $<$ 根ノードの値 $<$ 右部分木のすべてのノードの値。 -2. 任意のノードの左と右の部分木も二分探索木です。つまり、条件`1.`も満たします。 +2. 任意のノードの左部分木と右部分木も二分探索木であり、すなわち条件 `1.` も満たします。 ![二分探索木](binary_search_tree.assets/binary_search_tree.png) ## 二分探索木の操作 -二分探索木をクラス`BinarySearchTree`としてカプセル化し、木の根ノードを指すメンバー変数`root`を宣言します。 +二分探索木をクラス `BinarySearchTree` としてカプセル化し、木の根ノードを指すメンバ変数 `root` を宣言します。 -### ノードの検索 +### ノードの探索 -ターゲットノード値`num`が与えられた場合、二分探索木の性質に従って検索できます。下図に示すように、ノード`cur`を宣言し、二分木の根ノード`root`から開始し、ノード値`cur.val`と`num`のサイズを比較するループを行います。 +目標ノードの値 `num` が与えられたら、二分探索木の性質に基づいて探索できます。以下の図に示すように、ノード `cur` を宣言し、二分木の根ノード `root` から出発して、ノード値 `cur.val` と `num` の大小関係を繰り返し比較します。 -- `cur.val < num`の場合、ターゲットノードは`cur`の右部分木にあることを意味するため、`cur = cur.right`を実行します。 -- `cur.val > num`の場合、ターゲットノードは`cur`の左部分木にあることを意味するため、`cur = cur.left`を実行します。 -- `cur.val = num`の場合、ターゲットノードが見つかったことを意味するため、ループを終了してノードを返します。 +- `cur.val < num` の場合、目標ノードは `cur` の右部分木にあるため、`cur = cur.right` を実行します。 +- `cur.val > num` の場合、目標ノードは `cur` の左部分木にあるため、`cur = cur.left` を実行します。 +- `cur.val = num` の場合、目標ノードが見つかったことを表し、ループを抜けてそのノードを返します。 === "<1>" - ![二分探索木でのノード検索例](binary_search_tree.assets/bst_search_step1.png) + ![二分探索木のノード探索例](binary_search_tree.assets/bst_search_step1.png) === "<2>" ![bst_search_step2](binary_search_tree.assets/bst_search_step2.png) @@ -31,7 +31,7 @@ === "<4>" ![bst_search_step4](binary_search_tree.assets/bst_search_step4.png) -二分探索木での検索操作は二分探索アルゴリズムと同じ原理で動作し、各ラウンドでケースの半分を排除します。ループ数は最大で二分木の高さです。二分木が平衡している場合、$O(\log n)$の時間を使用します。コード例は以下の通りです: +二分探索木の探索操作は二分探索アルゴリズムと同じ原理で動作し、各ラウンドで半分の候補を除外します。ループ回数の上限は二分木の高さであり、二分木が平衡であれば $O(\log n)$ 時間です。コード例は次のとおりです。 ```src [file]{binary_search_tree}-[class]{binary_search_tree}-[func]{search} @@ -39,45 +39,45 @@ ### ノードの挿入 -挿入する要素`num`が与えられた場合、二分探索木の性質「左部分木 < 根ノード < 右部分木」を維持するため、挿入操作は下図に示すように進行します。 +挿入する要素 `num` が与えられたとき、二分探索木の「左部分木 < 根ノード < 右部分木」という性質を保つため、挿入操作の流れは以下の図のようになります。 -1. **挿入位置を見つける**: 検索操作と同様に、根ノードから開始し、現在のノード値と`num`のサイズ関係に従って下向きにループし、葉ノードを通過(`None`に走査)するまで、ループを終了します。 -2. **この位置にノードを挿入**: ノード`num`を初期化し、`None`があった場所に配置します。 +1. **挿入位置を探索する**:探索操作と同様に、根ノードから出発し、現在のノード値と `num` の大小関係に基づいて下方向へ探索を繰り返し、葉ノードを越えて(`None` まで到達して)ループを抜けます。 +2. **その位置にノードを挿入する**:ノード `num` を初期化し、そのノードを `None` の位置に置きます。 -![二分探索木へのノード挿入](binary_search_tree.assets/bst_insert.png) +![二分探索木にノードを挿入する](binary_search_tree.assets/bst_insert.png) -コード実装では、以下の2点に注意してください。 +コード実装では、次の 2 点に注意が必要です。 -- 二分探索木は重複ノードの存在を許可しません。そうでなければ、その定義に違反します。したがって、挿入するノードが既に木に存在する場合、挿入は実行されず、ノードは直接戻ります。 -- 挿入操作を実行するには、前のループからのノードを保存するためにノード`pre`を使用する必要があります。このようにして、`None`に走査したときに、その親ノードを取得でき、ノード挿入操作を完了できます。 +- 二分探索木では重複ノードを許可しません。そうでないと定義に反するためです。したがって、挿入対象のノードが木内にすでに存在する場合は、挿入を行わずそのまま返します。 +- ノード挿入を実現するために、ノード `pre` を用いて前回のループのノードを保持する必要があります。これにより、`None` までたどり着いたときにその親ノードを取得でき、ノード挿入を完了できます。 ```src [file]{binary_search_tree}-[class]{binary_search_tree}-[func]{insert} ``` -ノードの検索と同様に、ノードの挿入には$O(\log n)$の時間を使用します。 +ノード探索と同様に、ノード挿入には $O(\log n)$ 時間を要します。 ### ノードの削除 -まず、二分木でターゲットノードを見つけ、それを削除します。ノードの挿入と同様に、削除操作が完了した後も、二分探索木の性質「左部分木 < 根ノード < 右部分木」が満たされることを保証する必要があります。したがって、ターゲットノードの子ノード数に基づいて、0、1、2の3つのケースに分け、対応するノード削除操作を実行します。 +まず二分木内で目標ノードを見つけ、その後で削除します。ノード挿入と同様に、削除操作の完了後も二分探索木の「左部分木 < 根ノード < 右部分木」という性質が保たれる必要があります。そのため、目標ノードの子ノード数に応じて、0、1、2 の 3 つのケースに分けて対応する削除操作を行います。 -下図に示すように、削除するノードの次数が$0$の場合、そのノードは葉ノードであることを意味し、直接削除できます。 +以下の図に示すように、削除対象ノードの次数が $0$ のとき、そのノードは葉ノードであり、直接削除できます。 -![二分探索木でのノード削除(次数0)](binary_search_tree.assets/bst_remove_case1.png) +![二分探索木でノードを削除する(次数 0 )](binary_search_tree.assets/bst_remove_case1.png) -下図に示すように、削除するノードの次数が$1$の場合、削除するノードをその子ノードで置き換えるだけで十分です。 +以下の図に示すように、削除対象ノードの次数が $1$ のとき、削除対象ノードをその子ノードで置き換えれば十分です。 -![二分探索木でのノード削除(次数1)](binary_search_tree.assets/bst_remove_case2.png) +![二分探索木でノードを削除する(次数 1 )](binary_search_tree.assets/bst_remove_case2.png) -削除するノードの次数が$2$の場合、直接削除することはできませんが、ノードを使用して置き換える必要があります。二分探索木の性質「左部分木 $<$ 根ノード $<$ 右部分木」を維持するため、**このノードは右部分木の最小ノードまたは左部分木の最大ノードのいずれかです**。 +削除対象ノードの次数が $2$ のときは、直接削除できず、別のノードでそのノードを置き換える必要があります。二分探索木の「左部分木 $<$ 根ノード $<$ 右部分木」という性質を保つ必要があるため、**このノードには右部分木の最小ノードまたは左部分木の最大ノードを使えます**。 -右部分木の最小ノード(中順走査での次のノード)を選択すると仮定すると、削除操作は下図に示すように進行します。 +右部分木の最小ノード(中順走査で次のノード)を選ぶと仮定すると、削除操作の流れは以下の図のようになります。 -1. 削除するノードの「中順走査シーケンス」での次のノードを見つけ、`tmp`として示します。 -2. 削除するノードの値を`tmp`の値で置き換え、木内でノード`tmp`を再帰的に削除します。 +1. 削除対象ノードの「中順走査列」における次のノードを見つけ、`tmp` と記します。 +2. `tmp` の値で削除対象ノードの値を上書きし、木の中でノード `tmp` を再帰的に削除します。 === "<1>" - ![二分探索木でのノード削除(次数2)](binary_search_tree.assets/bst_remove_case3_step1.png) + ![二分探索木でノードを削除する(次数 2 )](binary_search_tree.assets/bst_remove_case3_step1.png) === "<2>" ![bst_remove_case3_step2](binary_search_tree.assets/bst_remove_case3_step2.png) @@ -88,42 +88,42 @@ === "<4>" ![bst_remove_case3_step4](binary_search_tree.assets/bst_remove_case3_step4.png) -ノードを削除する操作も$O(\log n)$の時間を使用します。削除するノードを見つけるのに$O(\log n)$の時間が必要で、中順走査の後継ノードを取得するのに$O(\log n)$の時間が必要です。コード例は以下の通りです: +ノード削除操作も同様に $O(\log n)$ 時間を要します。削除対象ノードの探索に $O(\log n)$ 時間、中順走査の後続ノードの取得に $O(\log n)$ 時間が必要です。コード例は次のとおりです。 ```src [file]{binary_search_tree}-[class]{binary_search_tree}-[func]{remove} ``` -### 中順走査は順序付けされている +### 中順走査は昇順 -下図に示すように、二分木の中順走査は「左 $\rightarrow$ 根 $\rightarrow$ 右」の走査順序に従い、二分探索木は「左子ノード $<$ 根ノード $<$ 右子ノード」のサイズ関係を満たします。 +以下の図に示すように、二分木の中順走査は「左 $\rightarrow$ 根 $\rightarrow$ 右」という順序に従い、二分探索木は「左子ノード $<$ 根ノード $<$ 右子ノード」という大小関係を満たします。 -これは、二分探索木で中順走査を実行するときに、常に次に小さいノードが最初に走査されることを意味し、重要な性質につながります:**二分探索木の中順走査のシーケンスは昇順です**。 +これは、二分探索木で中順走査を行うと常に次の最小ノードが優先して走査されることを意味し、そこから重要な性質が導かれます。**二分探索木の中順走査列は昇順です**。 -中順走査の昇順性質を使用して、二分探索木で順序付けされたデータを取得するには$O(n)$の時間のみが必要で、追加のソート操作は不要であり、非常に効率的です。 +中順走査が昇順になる性質を利用すれば、二分探索木から整列済みデータを取得するのに必要な時間は $O(n)$ のみで、追加のソート操作は不要です。非常に効率的です。 -![二分探索木の中順走査シーケンス](binary_search_tree.assets/bst_inorder_traversal.png) +![二分探索木の中順走査列](binary_search_tree.assets/bst_inorder_traversal.png) ## 二分探索木の効率 -データのセットが与えられた場合、配列または二分探索木を使用して格納することを検討します。下の表を観察すると、二分探索木のすべての操作は対数時間計算量を持ち、安定して効率的です。配列は、頻繁な追加と検索や削除の頻度が少ないシナリオでのみ、二分探索木よりも効率的です。 +あるデータ集合が与えられたとき、配列または二分探索木で格納する場合を考えます。次の表を見ると、二分探索木の各操作の時間計算量はいずれも対数オーダーであり、安定して高効率です。高頻度の追加と低頻度の探索・削除という場面でのみ、配列のほうが二分探索木より効率的です。

  配列と探索木の効率比較

-| | 未ソート配列 | 二分探索木 | -| -------------- | -------------- | ------------------ | -| 要素の検索 | $O(n)$ | $O(\log n)$ | -| 要素の挿入 | $O(1)$ | $O(\log n)$ | -| 要素の削除 | $O(n)$ | $O(\log n)$ | +| | 無秩序配列 | 二分探索木 | +| -------- | -------- | ----------- | +| 要素の探索 | $O(n)$ | $O(\log n)$ | +| 要素の挿入 | $O(1)$ | $O(\log n)$ | +| 要素の削除 | $O(n)$ | $O(\log n)$ | -理想的には、二分探索木は「平衡」しており、任意のノードを$\log n$ループ内で見つけることができます。 +理想的な状況では、二分探索木は「平衡」しており、その場合は $\log n$ 回のループ内で任意のノードを探索できます。 -しかし、二分探索木で継続的にノードを挿入および削除すると、下図に示すように連結リストに退化する可能性があり、さまざまな操作の時間計算量も$O(n)$に悪化します。 +しかし、二分探索木でノードの挿入と削除を繰り返すと、二分木が以下の図のような連結リストへ退化する可能性があり、このとき各操作の時間計算量も $O(n)$ に退化します。 ![二分探索木の退化](binary_search_tree.assets/bst_degradation.png) -## 二分探索木の一般的な応用 +## 二分探索木の代表的な応用 -- システムでの多レベルインデックスとして使用され、効率的な検索、挿入、削除操作を実装します。 -- 特定の検索アルゴリズムの基盤となるデータ構造として機能します。 -- データストリームを格納して、その順序付けされた状態を維持するために使用されます。 +- システム内の多段インデックスとして用いられ、効率的な探索、挿入、削除操作を実現します。 +- 一部の探索アルゴリズムの基盤データ構造として使われます。 +- データストリームを格納し、その順序状態を保つために使われます。 diff --git a/ja/docs/chapter_tree/binary_tree.md b/ja/docs/chapter_tree/binary_tree.md index f8cc93925..479f5b1dc 100644 --- a/ja/docs/chapter_tree/binary_tree.md +++ b/ja/docs/chapter_tree/binary_tree.md @@ -1,26 +1,26 @@ # 二分木 -二分木は、祖先と子孫の間の階層関係を表現し、「二つに分割する」分割統治法の論理を体現する非線形データ構造です。連結リストと同様に、二分木の基本単位はノードであり、各ノードは値、左の子ノードへの参照、右の子ノードへの参照を含みます。 +二分木(binary tree)は非線形データ構造の一種であり、「祖先」と「子孫」の派生関係を表し、「一つを二つに分ける」分割統治の考え方を体現しています。連結リストと同様に、二分木の基本単位はノードであり、各ノードは値、左子ノードへの参照、右子ノードへの参照を含みます。 === "Python" ```python title="" class TreeNode: - """二分木ノード""" + """二分木ノードクラス""" def __init__(self, val: int): self.val: int = val # ノード値 - self.left: TreeNode | None = None # 左の子ノードへの参照 - self.right: TreeNode | None = None # 右の子ノードへの参照 + self.left: TreeNode | None = None # 左子ノード参照 + self.right: TreeNode | None = None # 右子ノード参照 ``` === "C++" ```cpp title="" - /* 二分木ノード */ + /* 二分木ノード構造体 */ struct TreeNode { int val; // ノード値 - TreeNode *left; // 左の子ノードへのポインタ - TreeNode *right; // 右の子ノードへのポインタ + TreeNode *left; // 左子ノードポインタ + TreeNode *right; // 右子ノードポインタ TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} }; ``` @@ -28,11 +28,11 @@ === "Java" ```java title="" - /* 二分木ノード */ + /* 二分木ノードクラス */ class TreeNode { int val; // ノード値 - TreeNode left; // 左の子ノードへの参照 - TreeNode right; // 右の子ノードへの参照 + TreeNode left; // 左子ノード参照 + TreeNode right; // 右子ノード参照 TreeNode(int x) { val = x; } } ``` @@ -40,18 +40,18 @@ === "C#" ```csharp title="" - /* 二分木ノード */ + /* 二分木ノードクラス */ class TreeNode(int? x) { public int? val = x; // ノード値 - public TreeNode? left; // 左の子ノードへの参照 - public TreeNode? right; // 右の子ノードへの参照 + public TreeNode? left; // 左子ノード参照 + public TreeNode? right; // 右子ノード参照 } ``` === "Go" ```go title="" - /* 二分木ノード */ + /* 二分木ノード構造体 */ type TreeNode struct { Val int Left *TreeNode @@ -60,8 +60,8 @@ /* コンストラクタ */ func NewTreeNode(v int) *TreeNode { return &TreeNode{ - Left: nil, // 左の子ノードへのポインタ - Right: nil, // 右の子ノードへのポインタ + Left: nil, // 左子ノードポインタ + Right: nil, // 右子ノードポインタ Val: v, // ノード値 } } @@ -70,11 +70,11 @@ === "Swift" ```swift title="" - /* 二分木ノード */ + /* 二分木ノードクラス */ class TreeNode { var val: Int // ノード値 - var left: TreeNode? // 左の子ノードへの参照 - var right: TreeNode? // 右の子ノードへの参照 + var left: TreeNode? // 左子ノード参照 + var right: TreeNode? // 右子ノード参照 init(x: Int) { val = x @@ -85,11 +85,11 @@ === "JS" ```javascript title="" - /* 二分木ノード */ + /* 二分木ノードクラス */ class TreeNode { val; // ノード値 - left; // 左の子ノードへのポインタ - right; // 右の子ノードへのポインタ + left; // 左子ノードポインタ + right; // 右子ノードポインタ constructor(val, left, right) { this.val = val === undefined ? 0 : val; this.left = left === undefined ? null : left; @@ -101,7 +101,7 @@ === "TS" ```typescript title="" - /* 二分木ノード */ + /* 二分木ノードクラス */ class TreeNode { val: number; left: TreeNode | null; @@ -109,8 +109,8 @@ constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { this.val = val === undefined ? 0 : val; // ノード値 - this.left = left === undefined ? null : left; // 左の子ノードへの参照 - this.right = right === undefined ? null : right; // 右の子ノードへの参照 + this.left = left === undefined ? null : left; // 左子ノード参照 + this.right = right === undefined ? null : right; // 右子ノード参照 } } ``` @@ -118,11 +118,11 @@ === "Dart" ```dart title="" - /* 二分木ノード */ + /* 二分木ノードクラス */ class TreeNode { int val; // ノード値 - TreeNode? left; // 左の子ノードへの参照 - TreeNode? right; // 右の子ノードへの参照 + TreeNode? left; // 左子ノード参照 + TreeNode? right; // 右子ノード参照 TreeNode(this.val, [this.left, this.right]); } ``` @@ -133,11 +133,11 @@ use std::rc::Rc; use std::cell::RefCell; - /* 二分木ノード */ + /* 二分木ノード構造体 */ struct TreeNode { val: i32, // ノード値 - left: Option>>, // 左の子ノードへの参照 - right: Option>>, // 右の子ノードへの参照 + left: Option>>, // 左子ノード参照 + right: Option>>, // 右子ノード参照 } impl TreeNode { @@ -155,12 +155,12 @@ === "C" ```c title="" - /* 二分木ノード */ + /* 二分木ノード構造体 */ typedef struct TreeNode { int val; // ノード値 int height; // ノードの高さ - struct TreeNode *left; // 左の子ノードへのポインタ - struct TreeNode *right; // 右の子ノードへのポインタ + struct TreeNode *left; // 左子ノードポインタ + struct TreeNode *right; // 右子ノードポインタ } TreeNode; /* コンストラクタ */ @@ -179,61 +179,70 @@ === "Kotlin" ```kotlin title="" - /* 二分木ノード */ + /* 二分木ノードクラス */ class TreeNode(val _val: Int) { // ノード値 - val left: TreeNode? = null // 左の子ノードへの参照 - val right: TreeNode? = null // 右の子ノードへの参照 + val left: TreeNode? = null // 左子ノード参照 + val right: TreeNode? = null // 右子ノード参照 } ``` === "Ruby" ```ruby title="" + ### 二分木ノードクラス ### + class TreeNode + attr_accessor :val # ノード値 + attr_accessor :left # 左子ノード参照 + attr_accessor :right # 右子ノード参照 + def initialize(val) + @val = val + end + end ``` -各ノードは2つの参照(ポインタ)を持ち、それぞれ左の子ノード右の子ノードを指しています。このノードは、これら2つの子ノードの親ノードと呼ばれます。二分木のノードが与えられたとき、このノードの左の子とその下にあるすべてのノードで形成される木を、このノードの左部分木と呼びます。同様に、右部分木も定義できます。 +各ノードは 2 つの参照(ポインタ)を持ち、それぞれ左子ノード(left-child node)右子ノード(right-child node)を指します。このノードはこれら 2 つの子ノードの親ノード(parent node)と呼ばれます。二分木のあるノードが与えられたとき、そのノードの左子ノードとその配下のノードからなる木をそのノードの左部分木(left subtree)と呼び、同様に右部分木(right subtree)が定義されます。 -**二分木では、葉ノードを除いて、他のすべてのノードは子ノードと空でない部分木を含みます。** 下図に示すように、「ノード2」を親ノードとして見ると、その左と右の子ノードはそれぞれ「ノード4」と「ノード5」です。左部分木は「ノード4」とその下にあるすべてのノードで形成され、右部分木は「ノード5」とその下にあるすべてのノードで形成されます。 +**二分木では、葉ノードを除くすべてのノードが子ノードと空でない部分木を持ちます**。以下の図に示すように、「ノード 2」を親ノードとみなすと、その左子ノードと右子ノードはそれぞれ「ノード 4」と「ノード 5」であり、左部分木は「ノード 4 とその配下のノードからなる木」、右部分木は「ノード 5 とその配下のノードからなる木」です。 ![親ノード、子ノード、部分木](binary_tree.assets/binary_tree_definition.png) -## 二分木の一般的な用語 +## 二分木のよく使われる用語 -二分木でよく使用される用語を下図に示します。 +二分木でよく使われる用語を以下の図に示します。 -- 根ノード:二分木の最上位レベルにあるノードで、親ノードを持ちません。 -- 葉ノード:子ノードを持たないノードで、両方のポインタが`None`を指しています。 -- :2つのノードを結ぶ線分で、ノード間の参照(ポインタ)を表現します。 -- ノードのレベル:上から下に向かって増加し、根ノードがレベル1です。 -- ノードの次数:ノードが持つ子ノードの数です。二分木では、次数は0、1、または2になります。 -- 二分木の高さ:根ノードから最も遠い葉ノードまでの辺の数です。 -- ノードの深さ:根ノードからそのノードまでの辺の数です。 -- ノードの高さ:最も遠い葉ノードからそのノードまでの辺の数です。 +- 根ノード(root node):二分木の最上位にあるノードで、親ノードを持ちません。 +- 葉ノード(leaf node):子ノードを持たないノードで、2 本のポインタはいずれも `None` を指します。 +- 辺(edge):2 つのノードを結ぶ線分、すなわちノード参照(ポインタ)です。 +- ノードが属するレベル(level):上から下へ向かって増加し、根ノードのレベルは 1 です。 +- ノードの次数(degree):ノードの子ノードの数。二分木では次数の取り得る値は 0、1、2 です。 +- 二分木の高さ(height):根ノードから最も遠い葉ノードまでに通る辺の数。 +- ノードの深さ(depth):根ノードからそのノードまでに通る辺の数。 +- ノードの高さ(height):そのノードから最も遠い葉ノードまでに通る辺の数。 -![二分木の一般的な用語](binary_tree.assets/binary_tree_terminology.png) +![二分木のよく使われる用語](binary_tree.assets/binary_tree_terminology.png) !!! tip - 「高さ」と「深さ」は通常「通過する辺の数」として定義しますが、一部の問題や教科書では「通過するノードの数」として定義されることがあります。この場合、高さと深さの両方を1だけ増やす必要があります。 + なお、通常「高さ」と「深さ」は「通過した辺の数」と定義しますが、問題や教材によっては「通過したノードの数」と定義する場合もあります。その場合、高さと深さはいずれも 1 を加える必要があります。 ## 二分木の基本操作 -### 二分木の初期化 +### 二分木を初期化する -連結リストと同様に、二分木の初期化では、まずノードを作成し、次にそれらの間の参照(ポインタ)を確立します。 +連結リストと同様に、まずノードを初期化し、その後で参照(ポインタ)を構築します。 === "Python" ```python title="binary_tree.py" - # 二分木の初期化 - # ノードの初期化 + # 二分木を初期化する + # ノードを初期化する n1 = TreeNode(val=1) n2 = TreeNode(val=2) n3 = TreeNode(val=3) n4 = TreeNode(val=4) n5 = TreeNode(val=5) - # ノード間の参照(ポインタ)を結ぶ + # ノード間の参照(ポインタ)を構築する n1.left = n2 n1.right = n3 n2.left = n4 @@ -243,14 +252,14 @@ === "C++" ```cpp title="binary_tree.cpp" - /* 二分木の初期化 */ - // ノードの初期化 + /* 二分木を初期化する */ + // ノードを初期化する TreeNode* n1 = new TreeNode(1); TreeNode* n2 = new TreeNode(2); TreeNode* n3 = new TreeNode(3); TreeNode* n4 = new TreeNode(4); TreeNode* n5 = new TreeNode(5); - // ノード間の参照(ポインタ)を結ぶ + // ノード間の参照(ポインタ)を構築する n1->left = n2; n1->right = n3; n2->left = n4; @@ -260,13 +269,13 @@ === "Java" ```java title="binary_tree.java" - // ノードの初期化 + // ノードを初期化する TreeNode n1 = new TreeNode(1); TreeNode n2 = new TreeNode(2); TreeNode n3 = new TreeNode(3); TreeNode n4 = new TreeNode(4); TreeNode n5 = new TreeNode(5); - // ノード間の参照(ポインタ)を結ぶ + // ノード間の参照(ポインタ)を構築する n1.left = n2; n1.right = n3; n2.left = n4; @@ -276,14 +285,14 @@ === "C#" ```csharp title="binary_tree.cs" - /* 二分木の初期化 */ - // ノードの初期化 + /* 二分木を初期化する */ + // ノードを初期化する TreeNode n1 = new(1); TreeNode n2 = new(2); TreeNode n3 = new(3); TreeNode n4 = new(4); TreeNode n5 = new(5); - // ノード間の参照(ポインタ)を結ぶ + // ノード間の参照(ポインタ)を構築する n1.left = n2; n1.right = n3; n2.left = n4; @@ -293,14 +302,14 @@ === "Go" ```go title="binary_tree.go" - /* 二分木の初期化 */ - // ノードの初期化 + /* 二分木を初期化する */ + // ノードを初期化する n1 := NewTreeNode(1) n2 := NewTreeNode(2) n3 := NewTreeNode(3) n4 := NewTreeNode(4) n5 := NewTreeNode(5) - // ノード間の参照(ポインタ)を結ぶ + // ノード間の参照(ポインタ)を構築する n1.Left = n2 n1.Right = n3 n2.Left = n4 @@ -310,13 +319,13 @@ === "Swift" ```swift title="binary_tree.swift" - // ノードの初期化 + // ノードを初期化する let n1 = TreeNode(x: 1) let n2 = TreeNode(x: 2) let n3 = TreeNode(x: 3) let n4 = TreeNode(x: 4) let n5 = TreeNode(x: 5) - // ノード間の参照(ポインタ)を結ぶ + // ノード間の参照(ポインタ)を構築する n1.left = n2 n1.right = n3 n2.left = n4 @@ -326,14 +335,14 @@ === "JS" ```javascript title="binary_tree.js" - /* 二分木の初期化 */ - // ノードの初期化 + /* 二分木を初期化する */ + // ノードを初期化する let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); - // ノード間の参照(ポインタ)を結ぶ + // ノード間の参照(ポインタ)を構築する n1.left = n2; n1.right = n3; n2.left = n4; @@ -343,14 +352,14 @@ === "TS" ```typescript title="binary_tree.ts" - /* 二分木の初期化 */ - // ノードの初期化 + /* 二分木を初期化する */ + // ノードを初期化する let n1 = new TreeNode(1), n2 = new TreeNode(2), n3 = new TreeNode(3), n4 = new TreeNode(4), n5 = new TreeNode(5); - // ノード間の参照(ポインタ)を結ぶ + // ノード間の参照(ポインタ)を構築する n1.left = n2; n1.right = n3; n2.left = n4; @@ -360,14 +369,14 @@ === "Dart" ```dart title="binary_tree.dart" - /* 二分木の初期化 */ - // ノードの初期化 + /* 二分木を初期化する */ + // ノードを初期化する TreeNode n1 = new TreeNode(1); TreeNode n2 = new TreeNode(2); TreeNode n3 = new TreeNode(3); TreeNode n4 = new TreeNode(4); TreeNode n5 = new TreeNode(5); - // ノード間の参照(ポインタ)を結ぶ + // ノード間の参照(ポインタ)を構築する n1.left = n2; n1.right = n3; n2.left = n4; @@ -377,13 +386,13 @@ === "Rust" ```rust title="binary_tree.rs" - // ノードの初期化 + // ノードを初期化する let n1 = TreeNode::new(1); let n2 = TreeNode::new(2); let n3 = TreeNode::new(3); let n4 = TreeNode::new(4); let n5 = TreeNode::new(5); - // ノード間の参照(ポインタ)を結ぶ + // ノード間の参照(ポインタ)を構築する n1.borrow_mut().left = Some(n2.clone()); n1.borrow_mut().right = Some(n3); n2.borrow_mut().left = Some(n4); @@ -393,14 +402,14 @@ === "C" ```c title="binary_tree.c" - /* 二分木の初期化 */ - // ノードの初期化 + /* 二分木を初期化する */ + // ノードを初期化する TreeNode *n1 = newTreeNode(1); TreeNode *n2 = newTreeNode(2); TreeNode *n3 = newTreeNode(3); TreeNode *n4 = newTreeNode(4); TreeNode *n5 = newTreeNode(5); - // ノード間の参照(ポインタ)を結ぶ + // ノード間の参照(ポインタ)を構築する n1->left = n2; n1->right = n3; n2->left = n4; @@ -410,13 +419,13 @@ === "Kotlin" ```kotlin title="binary_tree.kt" - // ノードの初期化 + // ノードを初期化する val n1 = TreeNode(1) val n2 = TreeNode(2) val n3 = TreeNode(3) val n4 = TreeNode(4) val n5 = TreeNode(5) - // ノード間の参照(ポインタ)を結ぶ + // ノード間の参照(ポインタ)を構築する n1.left = n2 n1.right = n3 n2.left = n4 @@ -426,24 +435,39 @@ === "Ruby" ```ruby title="binary_tree.rb" - + # 二分木を初期化する + # ノードを初期化する + n1 = TreeNode.new(1) + n2 = TreeNode.new(2) + n3 = TreeNode.new(3) + n4 = TreeNode.new(4) + n5 = TreeNode.new(5) + # ノード間の参照(ポインタ)を構築する + n1.left = n2 + n1.right = n3 + n2.left = n4 + n2.right = n5 ``` +??? pythontutor "実行の可視化" + + https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E8%8A%82%E7%82%B9%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E9%92%88%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + ### ノードの挿入と削除 -連結リストと同様に、二分木でのノードの挿入と削除はポインタを変更することで実現できます。下図に例を示します。 +連結リストと同様に、二分木でのノードの挿入と削除はポインタを変更することで実現できます。以下の図に 1 つの例を示します。 -![二分木でのノードの挿入と削除](binary_tree.assets/binary_tree_add_remove.png) +![二分木でノードを挿入・削除する](binary_tree.assets/binary_tree_add_remove.png) === "Python" ```python title="binary_tree.py" # ノードの挿入と削除 p = TreeNode(0) - # n1 -> n2の間にノードPを挿入 + # n1 -> n2 の間にノード P を挿入する n1.left = p p.left = n2 - # ノードPを削除 + # ノード P を削除する n1.left = n2 ``` @@ -452,21 +476,23 @@ ```cpp title="binary_tree.cpp" /* ノードの挿入と削除 */ TreeNode* P = new TreeNode(0); - // n1とn2の間にノードPを挿入 + // n1 -> n2 の間にノード P を挿入する n1->left = P; P->left = n2; - // ノードPを削除 + // ノード P を削除する n1->left = n2; + // メモリを解放する + delete P; ``` === "Java" ```java title="binary_tree.java" TreeNode P = new TreeNode(0); - // n1とn2の間にノードPを挿入 + // n1 -> n2 の間にノード P を挿入する n1.left = P; P.left = n2; - // ノードPを削除 + // ノード P を削除する n1.left = n2; ``` @@ -475,10 +501,10 @@ ```csharp title="binary_tree.cs" /* ノードの挿入と削除 */ TreeNode P = new(0); - // n1とn2の間にノードPを挿入 + // n1 -> n2 の間にノード P を挿入する n1.left = P; P.left = n2; - // ノードPを削除 + // ノード P を削除する n1.left = n2; ``` @@ -486,11 +512,11 @@ ```go title="binary_tree.go" /* ノードの挿入と削除 */ - // n1とn2の間にノードPを挿入 + // n1 -> n2 の間にノード P を挿入する p := NewTreeNode(0) n1.Left = p p.Left = n2 - // ノードPを削除 + // ノード P を削除する n1.Left = n2 ``` @@ -498,10 +524,10 @@ ```swift title="binary_tree.swift" let P = TreeNode(x: 0) - // n1とn2の間にノードPを挿入 + // n1 -> n2 の間にノード P を挿入する n1.left = P P.left = n2 - // ノードPを削除 + // ノード P を削除する n1.left = n2 ``` @@ -510,10 +536,10 @@ ```javascript title="binary_tree.js" /* ノードの挿入と削除 */ let P = new TreeNode(0); - // n1とn2の間にノードPを挿入 + // n1 -> n2 の間にノード P を挿入する n1.left = P; P.left = n2; - // ノードPを削除 + // ノード P を削除する n1.left = n2; ``` @@ -522,10 +548,10 @@ ```typescript title="binary_tree.ts" /* ノードの挿入と削除 */ const P = new TreeNode(0); - // n1とn2の間にノードPを挿入 + // n1 -> n2 の間にノード P を挿入する n1.left = P; P.left = n2; - // ノードPを削除 + // ノード P を削除する n1.left = n2; ``` @@ -534,10 +560,10 @@ ```dart title="binary_tree.dart" /* ノードの挿入と削除 */ TreeNode P = new TreeNode(0); - // n1とn2の間にノードPを挿入 + // n1 -> n2 の間にノード P を挿入する n1.left = P; P.left = n2; - // ノードPを削除 + // ノード P を削除する n1.left = n2; ``` @@ -545,10 +571,10 @@ ```rust title="binary_tree.rs" let p = TreeNode::new(0); - // n1とn2の間にノードPを挿入 + // n1 -> n2 の間にノード P を挿入する n1.borrow_mut().left = Some(p.clone()); p.borrow_mut().left = Some(n2.clone()); - // ノードPを削除 + // ノード p を削除する n1.borrow_mut().left = Some(n2); ``` @@ -557,80 +583,92 @@ ```c title="binary_tree.c" /* ノードの挿入と削除 */ TreeNode *P = newTreeNode(0); - // n1とn2の間にノードPを挿入 + // n1 -> n2 の間にノード P を挿入する n1->left = P; P->left = n2; - // ノードPを削除 + // ノード P を削除する n1->left = n2; + // メモリを解放する + free(P); ``` === "Kotlin" ```kotlin title="binary_tree.kt" val P = TreeNode(0) - // n1とn2の間にノードPを挿入 + // n1 -> n2 の間にノード P を挿入する n1.left = P P.left = n2 - // ノードPを削除 + // ノード P を削除する n1.left = n2 ``` === "Ruby" ```ruby title="binary_tree.rb" - + # ノードの挿入と削除 + _p = TreeNode.new(0) + # n1 -> n2 の間にノード _p を挿入する + n1.left = _p + _p.left = n2 + # ノードを削除する + n1.left = n2 ``` +??? pythontutor "実行の可視化" + + https://pythontutor.com/render.html#code=class%20TreeNode%3A%0A%20%20%20%20%22%22%22%E4%BA%8C%E5%8F%89%E6%A0%91%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.left%3A%20TreeNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%B7%A6%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%20%20%20%20%20%20%20%20self.right%3A%20TreeNode%20%7C%20None%20%3D%20None%20%23%20%E5%8F%B3%E5%AD%90%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E8%8A%82%E7%82%B9%0A%20%20%20%20n1%20%3D%20TreeNode%28val%3D1%29%0A%20%20%20%20n2%20%3D%20TreeNode%28val%3D2%29%0A%20%20%20%20n3%20%3D%20TreeNode%28val%3D3%29%0A%20%20%20%20n4%20%3D%20TreeNode%28val%3D4%29%0A%20%20%20%20n5%20%3D%20TreeNode%28val%3D5%29%0A%20%20%20%20%23%20%E6%9E%84%E5%BB%BA%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%BC%95%E7%94%A8%EF%BC%88%E6%8C%87%E9%92%88%EF%BC%89%0A%20%20%20%20n1.left%20%3D%20n2%0A%20%20%20%20n1.right%20%3D%20n3%0A%20%20%20%20n2.left%20%3D%20n4%0A%20%20%20%20n2.right%20%3D%20n5%0A%0A%20%20%20%20%23%20%E6%8F%92%E5%85%A5%E4%B8%8E%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%0A%20%20%20%20p%20%3D%20TreeNode%280%29%0A%20%20%20%20%23%20%E5%9C%A8%20n1%20-%3E%20n2%20%E4%B8%AD%E9%97%B4%E6%8F%92%E5%85%A5%E8%8A%82%E7%82%B9%20P%0A%20%20%20%20n1.left%20%3D%20p%0A%20%20%20%20p.left%20%3D%20n2%0A%20%20%20%20%23%20%E5%88%A0%E9%99%A4%E8%8A%82%E7%82%B9%20P%0A%20%20%20%20n1.left%20%3D%20n2&cumulative=false&curInstr=37&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false + !!! tip - ノードの挿入は二分木の元の論理構造を変更する可能性があり、ノードの削除は通常そのノードとそのすべての部分木を削除することになることに注意してください。したがって、二分木では、挿入と削除は通常一連の操作を通じて実行され、意味のある結果を得ます。 + 注意すべき点として、ノードの挿入は二分木の元の論理構造を変える可能性があり、ノードの削除は通常、そのノードと配下のすべての部分木の削除を意味します。そのため、二分木における挿入と削除は、実際に意味のある操作を実現するために、通常は一連の操作を組み合わせて行います。 -## 二分木の一般的な種類 +## 一般的な二分木の種類 + +### 充足二分木 + +以下の図に示すように、充足二分木(perfect binary tree)ではすべてのレベルのノードが完全に埋まっています。充足二分木では、葉ノードの次数は $0$ で、それ以外のすべてのノードの次数は $2$ です。木の高さを $h$ とすると、ノード総数は $2^{h+1} - 1$ となり、標準的な指数関係を示して、自然界でよく見られる細胞分裂の現象を反映しています。 + +!!! tip + + なお、中国語圏では充足二分木は満二分木と呼ばれることもあります。 + +![充足二分木](binary_tree.assets/perfect_binary_tree.png) ### 完全二分木 -下図に示すように、完全二分木では、すべてのレベルがノードで完全に埋められています。完全二分木では、葉ノードの次数は$0$で、他のすべてのノードの次数は$2$です。ノードの総数は$2^{h+1} - 1$として計算でき、ここで$h$は木の高さです。これは標準的な指数関係を示し、自然界の細胞分裂の一般的な現象を反映しています。 +以下の図に示すように、完全二分木(complete binary tree)では最下層のノードだけが完全に埋まっていなくてもよく、しかも最下層のノードは左から右へ連続して詰められていなければなりません。なお、充足二分木も完全二分木の一種です。 -!!! tip +![完全二分木](binary_tree.assets/complete_binary_tree.png) - 中国語圏では、完全二分木はしばしば満二分木と呼ばれることに注意してください。 +### 充満二分木 -![完全二分木](binary_tree.assets/perfect_binary_tree.png) +以下の図に示すように、充満二分木(full binary tree)では、葉ノードを除くすべてのノードが 2 つの子ノードを持ちます。 -### 完備二分木 - -下図に示すように、完備二分木は、最下位レベルのみが完全に埋められていない可能性がある二分木で、最下位レベルのノードは左から右に連続して埋められる必要があります。完全二分木は完備二分木でもあることに注意してください。 - -![完備二分木](binary_tree.assets/complete_binary_tree.png) - -### 満二分木 - -下図に示すように、満二分木では、葉ノードを除いて、他のすべてのノードが2つの子ノードを持ちます。 - -![満二分木](binary_tree.assets/full_binary_tree.png) +![充満二分木](binary_tree.assets/full_binary_tree.png) ### 平衡二分木 -下図に示すように、平衡二分木では、任意のノードの左と右の部分木の高さの絶対差が1を超えません。 +以下の図に示すように、平衡二分木(balanced binary tree)では、任意のノードについて左部分木と右部分木の高さの差の絶対値が 1 を超えません。 ![平衡二分木](binary_tree.assets/balanced_binary_tree.png) ## 二分木の退化 -下図は、二分木の理想的な構造と退化した構造を示しています。二分木は、すべてのレベルが埋められているときに「完全二分木」になり、すべてのノードが一方に偏っているときに「連結リスト」に退化します。 +以下の図は、二分木の理想的な構造と退化した構造を示しています。二分木の各レベルのノードがすべて埋まっていると「充足二分木」となり、すべてのノードが片側に偏ると二分木は「連結リスト」へ退化します。 -- 完全二分木は、二分木の「分割統治法」の利点を十分に活用できる理想的なシナリオです。 -- 一方、連結リストは別の極端を表し、すべての操作が線形になり、時間計算量が$O(n)$になります。 +- 充足二分木は理想的なケースであり、二分木の「分割統治」の利点を十分に発揮できます。 +- 連結リストはその対極にあり、各種操作はすべて線形操作となり、時間計算量は $O(n)$ まで退化します。 -![二分木の最良と最悪の構造](binary_tree.assets/binary_tree_best_worst_cases.png) +![二分木の最良構造と最悪構造](binary_tree.assets/binary_tree_best_worst_cases.png) -下表に示すように、最良と最悪の構造では、二分木は葉ノード数、総ノード数、高さの最大値または最小値を達成します。 +以下の表に示すように、最良構造と最悪構造では、二分木の葉ノード数、ノード総数、高さなどが極大または極小になります。 -

  二分木の最良と最悪の構造

+

  二分木の最良構造と最悪構造

-| | 完全二分木 | 連結リスト | -| ----------------------------------------------- | ------------------ | ----------- | -| レベル$i$のノード数 | $2^{i-1}$ | $1$ | -| 高さ$h$の木の葉ノード数 | $2^h$ | $1$ | -| 高さ$h$の木の総ノード数 | $2^{h+1} - 1$ | $h + 1$ | -| 総ノード数$n$の木の高さ | $\log_2 (n+1) - 1$ | $n - 1$ | +| | 充足二分木 | 連結リスト | +| --------------------------- | ------------------ | ------- | +| 第 $i$ レベルのノード数 | $2^{i-1}$ | $1$ | +| 高さ $h$ の木の葉ノード数 | $2^h$ | $1$ | +| 高さ $h$ の木のノード総数 | $2^{h+1} - 1$ | $h + 1$ | +| ノード総数 $n$ の木の高さ | $\log_2 (n+1) - 1$ | $n - 1$ | diff --git a/ja/docs/chapter_tree/binary_tree_traversal.md b/ja/docs/chapter_tree/binary_tree_traversal.md index 04a791b0c..f6d2bdb14 100644 --- a/ja/docs/chapter_tree/binary_tree_traversal.md +++ b/ja/docs/chapter_tree/binary_tree_traversal.md @@ -1,41 +1,41 @@ # 二分木の走査 -物理的構造の観点から見ると、木は連結リストに基づくデータ構造です。したがって、その走査方法はポインタを通してノードに一つずつアクセスすることを含みます。しかし、木は非線形データ構造であるため、木の走査は連結リストの走査よりも複雑で、検索アルゴリズムの支援が必要です。 +物理構造の観点から見ると、木は連結リストを基盤としたデータ構造であり、その走査はポインタを通じてノードへ順にアクセスすることで行われます。しかし、木は非線形データ構造であるため、木の走査は連結リストの走査よりも複雑であり、検索アルゴリズムを用いて実現する必要があります。 -二分木の一般的な走査方法には、レベル順走査、前順走査、中順走査、後順走査があります。 +二分木の一般的な走査方法には、レベル順走査、先行順走査、中間順走査、後行順走査などがあります。 ## レベル順走査 -下図に示すように、レベル順走査は二分木を上から下へ、層ごとに走査します。各レベル内では、左から右へノードを訪問します。 +次の図に示すように、レベル順走査(level-order traversal)では、二分木を上から下へ層ごとに走査し、各層では左から右の順にノードへアクセスします。 -レベル順走査は本質的に幅優先走査の一種で、幅優先探索(BFS)とも呼ばれ、「周囲に向かって外向きに拡張する」層ごとの走査方法を体現しています。 +レベル順走査は本質的に幅優先走査(breadth-first traversal)に属し、幅優先探索(breadth-first search, BFS)とも呼ばれます。これは「同心円状に外側へ広がる」ような、層ごとの走査方法を表しています。 ![二分木のレベル順走査](binary_tree_traversal.assets/binary_tree_bfs.png) -### コード実装 +### コードの実装 -幅優先走査は通常「キュー」の助けを借りて実装されます。キューは「先入れ先出し」の規則に従い、幅優先走査は「層ごとの進行」規則に従います。両者の基本的な考え方は一致しています。実装コードは以下の通りです: +幅優先走査は通常「キュー」を用いて実装します。キューは「先入れ先出し」の規則に従い、幅優先走査は「層ごとに進む」という規則に従います。両者の背後にある考え方は一致しています。実装コードは次のとおりです: ```src [file]{binary_tree_bfs}-[class]{}-[func]{level_order} ``` -### 計算量分析 +### 計算量 -- **時間計算量は$O(n)$**: すべてのノードが一度ずつ訪問され、$O(n)$の時間がかかります。ここで$n$はノード数です。 -- **空間計算量は$O(n)$**: 最悪の場合、つまり完全二分木の場合、最下位レベルに走査する前に、キューは最大$(n + 1) / 2$個のノードを同時に含むことができ、$O(n)$の空間を占有します。 +- **時間計算量は $O(n)$** :すべてのノードを1回ずつ訪問するため、計算量は $O(n)$ です。ここで、$n$ はノード数です。 +- **空間計算量は $O(n)$** :最悪の場合、すなわち完全二分木では、最下層に到達する前に、キュー内には最大で同時に $(n + 1) / 2$ 個のノードが存在し、$O(n)$ の空間を使用します。 -## 前順、中順、後順走査 +## 先行順・中間順・後行順走査 -対応して、前順、中順、後順走査はすべて深度優先走査に属し、深度優先探索(DFS)とも呼ばれ、「まず最後まで進み、その後バックトラックして続行する」走査方法を体現しています。 +同様に、先行順・中間順・後行順走査はいずれも深度優先走査(depth-first traversal)に属し、深度優先探索(depth-first search, DFS)とも呼ばれます。これは「まず行き止まりまで進み、その後で戻って続ける」という走査方法を表しています。 -下図は二分木に対して深度優先走査を実行する動作原理を示しています。**深度優先走査は二分木全体を「歩き回る」ようなもので**、各ノードで3つの位置に遭遇し、それらは前順、中順、後順走査に対応しています。 +次の図は、二分木に対して深度優先走査を行う仕組みを示しています。**深度優先走査は、二分木全体の外周をぐるりと「一周する」ようなものです**。各ノードでは3つの位置に出会い、それぞれが先行順走査・中間順走査・後行順走査に対応します。 -![二分探索木の前順、中順、後順走査](binary_tree_traversal.assets/binary_tree_dfs.png) +![二分探索木の先行順・中間順・後行順走査](binary_tree_traversal.assets/binary_tree_dfs.png) -### コード実装 +### コードの実装 -深度優先探索は通常再帰に基づいて実装されます: +深度優先探索は通常、再帰に基づいて実装されます: ```src [file]{binary_tree_dfs}-[class]{}-[func]{post_order} @@ -43,15 +43,15 @@ !!! tip - 深度優先探索は反復に基づいても実装できます。興味のある読者は自分で学習してください。 + 深度優先探索は反復によって実装することもできます。興味のある読者は自身で調べてみてください。 -下図は二分木の前順走査の再帰プロセスを示しており、これは「再帰」と「復帰」という2つの反対の部分に分けることができます。 +次の図は、二分木の先行順走査における再帰の過程を示しており、「行き」と「帰り」という2つの逆向きの部分に分けられます。 -1. 「再帰」は新しいメソッドを開始することを意味し、プログラムはこのプロセスで次のノードにアクセスします。 -2. 「復帰」は関数が戻ることを意味し、現在のノードが完全にアクセスされたことを示します。 +1. 「行き」は新しいメソッドの開始を表し、この過程でプログラムは次のノードにアクセスします。 +2. 「帰り」は関数の復帰を表し、現在のノードへのアクセスが完了したことを意味します。 === "<1>" - ![前順走査の再帰プロセス](binary_tree_traversal.assets/preorder_step1.png) + ![先行順走査の再帰過程](binary_tree_traversal.assets/preorder_step1.png) === "<2>" ![preorder_step2](binary_tree_traversal.assets/preorder_step2.png) @@ -83,7 +83,7 @@ === "<11>" ![preorder_step11](binary_tree_traversal.assets/preorder_step11.png) -### 計算量分析 +### 計算量 -- **時間計算量は$O(n)$**: すべてのノードが一度ずつ訪問され、$O(n)$の時間を使用します。 -- **空間計算量は$O(n)$**: 最悪の場合、つまり木が連結リストに退化した場合、再帰の深さは$n$に達し、システムは$O(n)$のスタックフレーム空間を占有します。 +- **時間計算量は $O(n)$** :すべてのノードを1回ずつ訪問するため、計算量は $O(n)$ です。 +- **空間計算量は $O(n)$** :最悪の場合、すなわち木が連結リストに退化したとき、再帰の深さは $n$ に達し、システムは $O(n)$ のスタックフレーム空間を使用します。 diff --git a/ja/docs/chapter_tree/index.md b/ja/docs/chapter_tree/index.md index f3648e51c..be68d97be 100644 --- a/ja/docs/chapter_tree/index.md +++ b/ja/docs/chapter_tree/index.md @@ -4,6 +4,6 @@ !!! abstract - そびえ立つ木は活力に満ちた本質を放ち、深い根と豊かな葉を誇りながらも、その枝は疎らに散らばり、幽玄な雰囲気を醸し出しています。 - - それはデータにおける分割統治の鮮やかな形を私たちに示しています。 + 大樹は生命力に満ち、根は深く葉は生い茂り、枝は豊かに広がる。 + + それはデータ分割統治の生き生きとした姿を私たちに示してくれる。 diff --git a/ja/docs/chapter_tree/summary.md b/ja/docs/chapter_tree/summary.md index d959b861b..08580a990 100644 --- a/ja/docs/chapter_tree/summary.md +++ b/ja/docs/chapter_tree/summary.md @@ -1,54 +1,54 @@ # まとめ -### 重要なポイント +### 要点の振り返り -- 二分木は非線形データ構造で、「一つを二つに分ける」分割統治のロジックを反映しています。各二分木ノードには値と2つのポインタが含まれ、それぞれ左と右の子ノードを指します。 -- 二分木のノードについて、その左(右)子ノードとその下に形成される木は、まとめてそのノードの左(右)部分木と呼ばれます。 -- 二分木に関連する用語には、根ノード、葉ノード、レベル、次数、エッジ、高さ、深さがあります。 -- 二分木の初期化、ノードの挿入、ノードの削除の操作は、連結リストの操作と似ています。 -- 一般的な二分木の種類には、完全二分木、完備二分木、満二分木、平衡二分木があります。完全二分木は理想的な状態を表し、連結リストは退化後の最悪の状態です。 -- 二分木は、ノード値と空きスロットをレベル順走査シーケンスで配置し、親ノードと子ノード間のインデックスマッピング関係に基づいてポインタを実装することで、配列を使用して表現できます。 -- 二分木のレベル順走査は幅優先探索手法で、「円を拡大しながら」の層ごとの走査方式を反映しています。通常はキューを使用して実装されます。 -- 前順、中順、後順走査はすべて深度優先探索手法で、「まず最後まで行き、その後バックトラックして続行する」走査方式を反映しています。通常は再帰を使用して実装されます。 -- 二分探索木は要素検索のための効率的なデータ構造で、検索、挿入、削除操作の時間計算量はすべて$O(\log n)$です。二分探索木が連結リストに退化すると、これらの時間計算量は$O(n)$に悪化します。 -- AVL木は平衡二分探索木とも呼ばれ、回転操作を通して継続的なノード挿入と削除後も木が平衡を保つことを保証します。 -- AVL木の回転操作には、右回転、左回転、右左回転、左右回転があります。ノードの挿入または削除後、AVL木はボトムアップ方式でこれらの回転を実行して自己平衡を取ります。 +- 二分木は非線形データ構造の一種であり、「二分する」分割統治の考え方を体現している。各二分木ノードは 1 つの値と 2 本のポインタを持ち、それぞれ左子ノードと右子ノードを指す。 +- 二分木のあるノードについて、その左(右)子ノードおよびその配下から構成される木を、そのノードの左(右)部分木と呼ぶ。 +- 二分木に関する用語には、根ノード、葉ノード、レベル、次数、辺、高さ、深さなどがある。 +- 二分木の初期化、ノードの挿入、ノードの削除は、連結リストの操作方法と似ている。 +- 一般的な二分木の種類には、perfect 二分木、complete 二分木、full 二分木、平衡二分木がある。perfect 二分木が最も理想的な状態であり、連結リストは退化後の最悪の状態である。 +- 二分木は配列で表現できる。方法としては、ノード値と空き位置をレベル順走査の順に並べ、親ノードと子ノードのインデックス対応関係に基づいてポインタを実現する。 +- 二分木のレベル順走査は幅優先探索の一種であり、「同心円状に外へ広がる」ような逐次的な走査方式を表しており、通常はキューによって実装される。 +- 前順、中順、後順走査はいずれも深さ優先探索に属し、「まず末端まで進み、その後バックトラックして続ける」という走査方式を体現しており、通常は再帰で実装される。 +- 二分探索木は効率的な要素探索データ構造であり、探索、挿入、削除の時間計算量はいずれも $O(\log n)$ である。二分探索木が連結リストへ退化すると、各操作の時間計算量は $O(n)$ まで悪化する。 +- AVL 木は平衡二分探索木とも呼ばれ、回転操作によって、ノードの挿入と削除を繰り返した後も木が平衡を保つようにしている。 +- AVL 木の回転操作には、右回転、左回転、右回転してから左回転、左回転してから右回転がある。ノードの挿入または削除の後、AVL 木は下から上へ回転操作を行い、木を再び平衡状態に戻す。 ### Q & A -**Q**: 一つのノードのみを持つ二分木について、木の高さと根ノードの深さの両方が$0$ですか? +**Q**:ノードが 1 つしかない二分木では、木の高さと根ノードの深さはどちらも $0$ ですか? -はい、高さと深さは通常「通過したエッジの数」として定義されるためです。 +はい。高さと深さは通常「通過した辺の本数」として定義されるからです。 -**Q**: 二分木における挿入と削除は一般的に一連の操作によって達成されます。ここでの「一連の操作」とは何を指しますか?子ノードのリソースを解放することを意味しますか? +**Q**:二分木における挿入と削除は通常一連の操作を組み合わせて完了しますが、ここでいう「一連の操作」とは何を指すのでしょうか?リソースの子ノードに対するリソース解放と理解できますか? -二分探索木を例に取ると、ノードを削除する操作は3つの異なるシナリオで処理する必要があり、それぞれ複数ステップのノード操作が必要です。 +二分探索木を例にすると、ノード削除は 3 つのケースに分けて処理する必要があり、各ケースで複数段階のノード操作が必要になります。 -**Q**: 二分木のDFS走査で前順、中順、後順の3つのシーケンスがあるのはなぜですか?その用途は何ですか? +**Q**:なぜ DFS による二分木走査には前順・中順・後順の 3 種類があり、それぞれどのような用途があるのですか? -配列の順次および逆順走査と同様に、前順、中順、後順走査は二分木を走査する3つの方法であり、特定の順序で走査結果を取得できます。例えば、二分探索木では、ノードサイズが「左子ノード値 < 根ノード値 < 右子ノード値」を満たすため、「左 $\rightarrow$ 根 $\rightarrow$ 右」の優先順位で木を走査することで、順序付けられたノードシーケンスを取得できます。 +配列の順方向走査と逆方向走査に似て、前順・中順・後順走査は二分木の 3 つの走査方法であり、特定の順序で走査結果を得るために使えます。たとえば二分探索木では、ノードの大小関係が `左子ノードの値 < 根ノードの値 < 右子ノードの値` を満たすため、「左 $\rightarrow$ 根 $\rightarrow$ 右」の優先順で木を走査すれば、整列済みのノード列を得られます。 -**Q**: 不平衡ノード`node`、`child`、`grand_child`間の関係を処理する右回転操作において、右回転後に`node`とその親ノード間の接続と`node`の元のリンクが失われるのではありませんか? +**Q**:右回転操作は不平衡ノード `node`、`child`、`grand_child` の関係を処理するものですが、`node` の親ノードと `node` の元の接続は維持しなくてよいのですか?右回転後に切れてしまいませんか? -この問題を再帰的な観点から見る必要があります。`right_rotate(root)`操作は部分木の根ノードを渡し、最終的に`return child`で回転された部分木の根ノードを返します。部分木の根ノードとその親ノード間の接続は、この関数が戻った後に確立され、これは右回転操作の保守範囲外です。 +この問題は再帰の視点から考える必要があります。右回転操作 `right_rotate(root)` に渡されるのは部分木の根ノードであり、最終的に `return child` によって回転後の部分木の根ノードを返します。部分木の根ノードとその親ノードの接続は、この関数の返却後に行われるため、右回転操作自身が管理する範囲には含まれません。 -**Q**: C++では、関数は`private`と`public`セクションに分かれています。これにはどのような考慮事項がありますか?なぜ`height()`関数と`updateHeight()`関数がそれぞれ`public`と`private`に配置されているのですか? +**Q**:C++ では関数を `private` と `public` に分けますが、この設計にはどのような考えがありますか?なぜ `height()` 関数と `updateHeight()` 関数をそれぞれ `public` と `private` に置くのですか? -これはメソッドの使用範囲によります。メソッドがクラス内でのみ使用される場合、`private`に設計されます。例えば、ユーザーが独自に`updateHeight()`を呼び出すことは意味がありません。これは挿入または削除操作の一ステップに過ぎないからです。しかし、`height()`はノードの高さにアクセスするためのもので、`vector.size()`と同様であるため、使用のために`public`に設定されています。 +主に、そのメソッドの利用範囲を見て決めます。メソッドがクラス内部でしか使われないなら、`private` に設計します。たとえば、利用者が `updateHeight()` を単独で呼び出しても意味はなく、これは挿入や削除の途中の 1 ステップにすぎません。一方で `height()` はノードの高さにアクセスするためのもので、`vector.size()` に似た役割を持つため、使いやすいように `public` に設定します。 -**Q**: 入力データのセットから二分探索木をどのように構築しますか?根ノードの選択は非常に重要ですか? +**Q**:入力データの集合から二分探索木をどのように構築しますか?根ノードの選び方は重要ですか? -はい、木を構築する方法は二分探索木コードの`build_tree()`メソッドで提供されています。根ノードの選択については、通常入力データをソートし、中央の要素を根ノードとして選択し、再帰的に左と右の部分木を構築します。このアプローチは木の平衡を最大化します。 +はい。木の構築方法は、二分探索木のコード中の `build_tree()` メソッドですでに示されています。根ノードの選択については、通常は入力データをソートし、その中央の要素を根ノードにしてから、左右の部分木を再帰的に構築します。こうすることで、木の平衡性を最大限に保てます。 -**Q**: Javaでは、文字列比較に常に`equals()`メソッドを使用する必要がありますか? +**Q**:Java では、文字列比較には必ず `equals()` メソッドを使うべきですか? -Javaでは、プリミティブデータ型の場合、`==`は2つの変数の値が等しいかどうかを比較するために使用されます。参照型の場合、2つのシンボルの動作原理は異なります。 +Java では、基本データ型については `==` を使って 2 つの変数の値が等しいかどうかを比較します。参照型については、この 2 つの記法の働き方は異なります。 -- `==`: 2つの変数が同じオブジェクトを指しているかどうか、つまりメモリ内の位置が同じかどうかを比較するために使用されます。 -- `equals()`: 2つのオブジェクトの値が等しいかどうかを比較するために使用されます。 +- `==` :2 つの変数が同じオブジェクトを指しているか、つまりメモリ上の位置が同じかどうかを比較するために使います。 +- `equals()`:2 つのオブジェクトの値が等しいかどうかを比較するために使います。 -したがって、値を比較するには`equals()`を使用すべきです。ただし、`String a = "hi"; String b = "hi";`で初期化された文字列は文字列定数プールに格納され、同じオブジェクトを指すため、`a == b`も2つの文字列の内容を比較するために使用できます。 +したがって、値を比較したい場合は `equals()` を使うべきです。ただし、`String a = "hi"; String b = "hi";` によって初期化された文字列は文字列定数プールに格納され、同じオブジェクトを指すため、`a == b` でも 2 つの文字列の内容を比較できます。 -**Q**: 最下位レベルに到達する前に、幅優先走査でキュー内のノード数は$2^h$ですか? +**Q**:幅優先走査で最下層に到達する前、キュー内のノード数は $2^h$ ですか? -はい、例えば高さ$h = 2$の満二分木は合計$n = 7$個のノードを持ち、最下位レベルには$4 = 2^h = (n + 1) / 2$個のノードがあります。 +はい。たとえば高さ $h = 2$ の充足二分木では、ノード総数は $n = 7$ であり、最下層のノード数は $4 = 2^h = (n + 1) / 2$ です。 diff --git a/ja/docs/index.md b/ja/docs/index.md index b58adcfe7..e11188ebe 100644 --- a/ja/docs/index.md +++ b/ja/docs/index.md @@ -1,5 +1,5 @@ # Hello アルゴリズム -アニメーションで図解し、ワンクリックで実行できるデータ構造とアルゴリズムのチュートリアル。 +アニメーション図解、ワンクリックで実行できるデータ構造とアルゴリズムのチュートリアル。 -[読む](chapter_hello_algo/) +[読み始める](chapter_hello_algo/) diff --git a/ja/mkdocs.yml b/ja/mkdocs.yml index 8494445e4..63bc2a6ac 100644 --- a/ja/mkdocs.yml +++ b/ja/mkdocs.yml @@ -4,7 +4,7 @@ INHERIT: ../mkdocs.yml # Project information site_name: Hello アルゴリズム site_url: https://www.hello-algo.com/ja/ -site_description: "アニメーションで図解、ワンクリック実行のデータ構造とアルゴリズムチュートリアル" +site_description: "アニメーション図解とワンクリック実行コードで学べるデータ構造とアルゴリズムの入門書" docs_dir: ../build/ja/docs site_dir: ../site/ja # Repository @@ -37,7 +37,7 @@ extra: # Page tree nav: - - 序: + - はじめに: - chapter_hello_algo/index.md - 第 0 章   前書き: # [icon: material/book-open-outline] @@ -48,13 +48,13 @@ nav: - 第 1 章   アルゴリズムを知る: # [icon: material/calculator-variant-outline] - chapter_introduction/index.md - - 1.1   アルゴリズムはどこにでもある: chapter_introduction/algorithms_are_everywhere.md - - 1.2   アルゴリズムとは何か: chapter_introduction/what_is_dsa.md + - 1.1   アルゴリズムは至るところにある: chapter_introduction/algorithms_are_everywhere.md + - 1.2   アルゴリズムとは: chapter_introduction/what_is_dsa.md - 1.3   まとめ: chapter_introduction/summary.md - 第 2 章   計算量解析: # [icon: material/timer-sand] - chapter_computational_complexity/index.md - - 2.1   アルゴリズムの効率評価: chapter_computational_complexity/performance_evaluation.md + - 2.1   アルゴリズム効率の評価: chapter_computational_complexity/performance_evaluation.md - 2.2   反復と再帰: chapter_computational_complexity/iteration_and_recursion.md - 2.3   時間計算量: chapter_computational_complexity/time_complexity.md - 2.4   空間計算量: chapter_computational_complexity/space_complexity.md @@ -64,8 +64,8 @@ nav: - chapter_data_structure/index.md - 3.1   データ構造の分類: chapter_data_structure/classification_of_data_structure.md - 3.2   基本データ型: chapter_data_structure/basic_data_types.md - - 3.3   数値の符号化 *: chapter_data_structure/number_encoding.md - - 3.4   文字の符号化 *: chapter_data_structure/character_encoding.md + - 3.3   数値エンコーディング *: chapter_data_structure/number_encoding.md + - 3.4   文字エンコーディング *: chapter_data_structure/character_encoding.md - 3.5   まとめ: chapter_data_structure/summary.md - 第 4 章   配列と連結リスト: # [icon: material/view-list-outline] @@ -82,10 +82,10 @@ nav: - 5.2   キュー: chapter_stack_and_queue/queue.md - 5.3   両端キュー: chapter_stack_and_queue/deque.md - 5.4   まとめ: chapter_stack_and_queue/summary.md - - 第 6 章   ハッシュ表: + - 第 6 章   ハッシュテーブル: # [icon: material/table-search] - chapter_hashing/index.md - - 6.1   ハッシュ表: chapter_hashing/hash_map.md + - 6.1   ハッシュテーブル: chapter_hashing/hash_map.md - 6.2   ハッシュ衝突: chapter_hashing/hash_collision.md - 6.3   ハッシュアルゴリズム: chapter_hashing/hash_algorithm.md - 6.4   まとめ: chapter_hashing/summary.md @@ -96,13 +96,13 @@ nav: - 7.2   二分木の走査: chapter_tree/binary_tree_traversal.md - 7.3   二分木の配列表現: chapter_tree/array_representation_of_tree.md - 7.4   二分探索木: chapter_tree/binary_search_tree.md - - 7.5   AVL木 *: chapter_tree/avl_tree.md + - 7.5   AVL 木 *: chapter_tree/avl_tree.md - 7.6   まとめ: chapter_tree/summary.md - 第 8 章   ヒープ: # [icon: material/family-tree] - chapter_heap/index.md - 8.1   ヒープ: chapter_heap/heap.md - - 8.2   ヒープ構築操作: chapter_heap/build_heap.md + - 8.2   ヒープ構築: chapter_heap/build_heap.md - 8.3   Top-k 問題: chapter_heap/top_k.md - 8.4   まとめ: chapter_heap/summary.md - 第 9 章   グラフ: @@ -116,10 +116,10 @@ nav: # [icon: material/text-search] - chapter_searching/index.md - 10.1   二分探索: chapter_searching/binary_search.md - - 10.2   二分探索の挿入点: chapter_searching/binary_search_insertion.md + - 10.2   二分探索の挿入位置: chapter_searching/binary_search_insertion.md - 10.3   二分探索の境界: chapter_searching/binary_search_edge.md - - 10.4   ハッシュ最適化戦略: chapter_searching/replace_linear_by_hashing.md - - 10.5   探索アルゴリズムの再認識: chapter_searching/searching_algorithm_revisited.md + - 10.4   ハッシュによる最適化戦略: chapter_searching/replace_linear_by_hashing.md + - 10.5   探索アルゴリズム再考: chapter_searching/searching_algorithm_revisited.md - 10.6   まとめ: chapter_searching/summary.md - 第 11 章   ソート: # [icon: material/sort-ascending] @@ -138,33 +138,33 @@ nav: - 第 12 章   分割統治: # [icon: material/set-split] - chapter_divide_and_conquer/index.md - - 12.1   分割統治アルゴリズム: chapter_divide_and_conquer/divide_and_conquer.md + - 12.1   分割統治法: chapter_divide_and_conquer/divide_and_conquer.md - 12.2   分割統治探索戦略: chapter_divide_and_conquer/binary_search_recur.md - - 12.3   木の構築問題: chapter_divide_and_conquer/build_binary_tree_problem.md - - 12.4   ハノイの塔問題: chapter_divide_and_conquer/hanota_problem.md + - 12.3   二分木の構築問題: chapter_divide_and_conquer/build_binary_tree_problem.md + - 12.4   ハノイの塔の問題: chapter_divide_and_conquer/hanota_problem.md - 12.5   まとめ: chapter_divide_and_conquer/summary.md - 第 13 章   バックトラッキング: # [icon: material/map-marker-path] - chapter_backtracking/index.md - 13.1   バックトラッキングアルゴリズム: chapter_backtracking/backtracking_algorithm.md - 13.2   全順列問題: chapter_backtracking/permutations_problem.md - - 13.3   部分集合和問題: chapter_backtracking/subset_sum_problem.md - - 13.4   Nクイーン問題: chapter_backtracking/n_queens_problem.md + - 13.3   部分和問題: chapter_backtracking/subset_sum_problem.md + - 13.4   n クイーン問題: chapter_backtracking/n_queens_problem.md - 13.5   まとめ: chapter_backtracking/summary.md - 第 14 章   動的計画法: # [icon: material/table-pivot] - chapter_dynamic_programming/index.md - - 14.1   動的計画法の初歩: chapter_dynamic_programming/intro_to_dynamic_programming.md - - 14.2   DP 問題の特性: chapter_dynamic_programming/dp_problem_features.md - - 14.3   DP の解法の考え方: chapter_dynamic_programming/dp_solution_pipeline.md - - 14.4   0-1ナップサック問題: chapter_dynamic_programming/knapsack_problem.md + - 14.1   動的計画法入門: chapter_dynamic_programming/intro_to_dynamic_programming.md + - 14.2   動的計画法の問題特性: chapter_dynamic_programming/dp_problem_features.md + - 14.3   動的計画法の問題解決の考え方: chapter_dynamic_programming/dp_solution_pipeline.md + - 14.4   0-1 ナップサック問題: chapter_dynamic_programming/knapsack_problem.md - 14.5   完全ナップサック問題: chapter_dynamic_programming/unbounded_knapsack_problem.md - 14.6   編集距離問題: chapter_dynamic_programming/edit_distance_problem.md - 14.7   まとめ: chapter_dynamic_programming/summary.md - 第 15 章   貪欲法: # [icon: material/head-heart-outline] - chapter_greedy/index.md - - 15.1   貪欲アルゴリズム: chapter_greedy/greedy_algorithm.md + - 15.1   貪欲法: chapter_greedy/greedy_algorithm.md - 15.2   分数ナップサック問題: chapter_greedy/fractional_knapsack_problem.md - 15.3   最大容量問題: chapter_greedy/max_capacity_problem.md - 15.4   最大積分割問題: chapter_greedy/max_product_cutting_problem.md @@ -173,7 +173,7 @@ nav: # [icon: material/help-circle-outline] - chapter_appendix/index.md - 16.1   プログラミング環境のインストール: chapter_appendix/installation.md - - 16.2   一緒に創作に参加する: chapter_appendix/contribution.md + - 16.2   一緒に制作に参加しましょう: chapter_appendix/contribution.md - 16.3   用語集: chapter_appendix/terminology.md - 参考文献: - chapter_reference/index.md diff --git a/overrides/main.html b/overrides/main.html index d67c25706..0a4550c8a 100644 --- a/overrides/main.html +++ b/overrides/main.html @@ -8,7 +8,7 @@ {% elif config.theme.language == 'en' %} {% set announcements = 'Welcome to contribute to Chinese-to-English translation! For more details, please refer to
CONTRIBUTING.md.' %} {% elif config.theme.language == 'ja' %} - {% set announcements = '日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。' %} + {% set announcements = '日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。' %} {% elif config.theme.language == 'ru' %} {% set announcements = 'Приглашаем вас участвовать в развитии русской версии! Подробнее здесь.' %} {% endif %}