From 954c45864b0e60d6834fde2292158ea39d899a54 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Fri, 17 Oct 2025 06:04:43 +0900 Subject: [PATCH] docs: add Japanese translate documents (#1812) * docs: add Japanese documents (`ja/docs`) * docs: add Japanese documents (`ja/codes`) * docs: add Japanese documents * Remove pythontutor blocks in ja/ * Add an empty at the end of each markdown file. * Add the missing figures (use the English version temporarily). * Add index.md for Japanese version. * Add index.html for Japanese version. * Add missing index.assets * Fix backtracking_algorithm.md for Japanese version. * Add avatar_eltociear.jpg. Fix image links on the Japanese landing page. * Add the Japanese banner. --------- Co-authored-by: krahets --- README.md | 2 + docs/assets/avatar/avatar_eltociear.jpg | Bin 0 -> 16126 bytes en/README.md | 2 + ja/CONTRIBUTING.md | 134 ++ ja/README.md | 91 ++ .../chapter_array_and_linkedlist/array.cpp | 113 ++ .../linked_list.cpp | 89 ++ .../cpp/chapter_array_and_linkedlist/list.cpp | 72 ++ .../chapter_array_and_linkedlist/my_list.cpp | 171 +++ .../cpp/chapter_backtracking/n_queens.cpp | 65 + .../chapter_backtracking/permutations_i.cpp | 54 + .../chapter_backtracking/permutations_ii.cpp | 56 + .../preorder_traversal_i_compact.cpp | 43 + .../preorder_traversal_ii_compact.cpp | 51 + .../preorder_traversal_iii_compact.cpp | 52 + .../preorder_traversal_iii_template.cpp | 79 ++ .../cpp/chapter_backtracking/subset_sum_i.cpp | 57 + .../subset_sum_i_naive.cpp | 55 + .../chapter_backtracking/subset_sum_ii.cpp | 62 + .../iteration.cpp | 76 ++ .../recursion.cpp | 78 ++ .../space_complexity.cpp | 107 ++ .../time_complexity.cpp | 168 +++ .../worst_best_time_complexity.cpp | 45 + .../binary_search_recur.cpp | 46 + .../chapter_divide_and_conquer/build_tree.cpp | 51 + .../cpp/chapter_divide_and_conquer/hanota.cpp | 66 + .../climbing_stairs_backtrack.cpp | 42 + .../climbing_stairs_constraint_dp.cpp | 37 + .../climbing_stairs_dfs.cpp | 32 + .../climbing_stairs_dfs_mem.cpp | 39 + .../climbing_stairs_dp.cpp | 49 + .../coin_change.cpp | 70 ++ .../coin_change_ii.cpp | 68 + .../edit_distance.cpp | 72 ++ .../chapter_dynamic_programming/knapsack.cpp | 84 ++ .../min_cost_climbing_stairs_dp.cpp | 57 + .../min_path_sum.cpp | 68 + .../unbounded_knapsack.cpp | 64 + .../chapter_graph/graph_adjacency_list.cpp | 90 ++ .../chapter_graph/graph_adjacency_matrix.cpp | 127 ++ ja/codes/cpp/chapter_graph/graph_bfs.cpp | 59 + ja/codes/cpp/chapter_graph/graph_dfs.cpp | 55 + .../cpp/chapter_greedy/coin_change_greedy.cpp | 60 + .../chapter_greedy/fractional_knapsack.cpp | 56 + ja/codes/cpp/chapter_greedy/max_capacity.cpp | 39 + .../chapter_greedy/max_product_cutting.cpp | 39 + .../cpp/chapter_hashing/array_hash_map.cpp | 110 ++ .../chapter_hashing/array_hash_map_test.cpp | 52 + .../cpp/chapter_hashing/built_in_hash.cpp | 29 + ja/codes/cpp/chapter_hashing/hash_map.cpp | 46 + .../cpp/chapter_hashing/hash_map_chaining.cpp | 150 +++ .../hash_map_open_addressing.cpp | 171 +++ ja/codes/cpp/chapter_hashing/simple_hash.cpp | 66 + ja/codes/cpp/chapter_heap/heap.cpp | 66 + ja/codes/cpp/chapter_heap/my_heap.cpp | 155 +++ ja/codes/cpp/chapter_heap/top_k.cpp | 38 + .../cpp/chapter_searching/binary_search.cpp | 59 + .../chapter_searching/binary_search_edge.cpp | 66 + .../binary_search_insertion.cpp | 66 + .../cpp/chapter_searching/hashing_search.cpp | 53 + .../cpp/chapter_searching/linear_search.cpp | 49 + ja/codes/cpp/chapter_searching/two_sum.cpp | 54 + ja/codes/cpp/chapter_sorting/bubble_sort.cpp | 56 + ja/codes/cpp/chapter_sorting/bucket_sort.cpp | 44 + .../cpp/chapter_sorting/counting_sort.cpp | 77 ++ ja/codes/cpp/chapter_sorting/heap_sort.cpp | 54 + .../cpp/chapter_sorting/insertion_sort.cpp | 31 + ja/codes/cpp/chapter_sorting/merge_sort.cpp | 58 + ja/codes/cpp/chapter_sorting/quick_sort.cpp | 166 +++ ja/codes/cpp/chapter_sorting/radix_sort.cpp | 65 + .../cpp/chapter_sorting/selection_sort.cpp | 34 + .../chapter_stack_and_queue/array_deque.cpp | 156 +++ .../chapter_stack_and_queue/array_queue.cpp | 129 ++ .../chapter_stack_and_queue/array_stack.cpp | 85 ++ .../cpp/chapter_stack_and_queue/deque.cpp | 46 + .../linkedlist_deque.cpp | 194 +++ .../linkedlist_queue.cpp | 120 ++ .../linkedlist_stack.cpp | 109 ++ .../cpp/chapter_stack_and_queue/queue.cpp | 41 + .../cpp/chapter_stack_and_queue/stack.cpp | 41 + .../cpp/chapter_tree/array_binary_tree.cpp | 137 ++ ja/codes/cpp/chapter_tree/avl_tree.cpp | 233 ++++ .../cpp/chapter_tree/binary_search_tree.cpp | 170 +++ ja/codes/cpp/chapter_tree/binary_tree.cpp | 43 + ja/codes/cpp/chapter_tree/binary_tree_bfs.cpp | 42 + ja/codes/cpp/chapter_tree/binary_tree_dfs.cpp | 69 + ja/codes/cpp/utils/common.hpp | 28 + ja/codes/cpp/utils/list_node.hpp | 42 + ja/codes/cpp/utils/print_utils.hpp | 228 ++++ ja/codes/cpp/utils/tree_node.hpp | 84 ++ ja/codes/cpp/utils/vertex.hpp | 36 + .../chapter_array_and_linkedlist/array.java | 105 ++ .../linked_list.java | 86 ++ .../chapter_array_and_linkedlist/list.java | 66 + .../chapter_array_and_linkedlist/my_list.java | 147 +++ .../java/chapter_backtracking/n_queens.java | 77 ++ .../chapter_backtracking/permutations_i.java | 51 + .../chapter_backtracking/permutations_ii.java | 53 + .../preorder_traversal_i_compact.java | 44 + .../preorder_traversal_ii_compact.java | 52 + .../preorder_traversal_iii_compact.java | 53 + .../preorder_traversal_iii_template.java | 77 ++ .../chapter_backtracking/subset_sum_i.java | 55 + .../subset_sum_i_naive.java | 53 + .../chapter_backtracking/subset_sum_ii.java | 60 + .../iteration.java | 76 ++ .../recursion.java | 79 ++ .../space_complexity.java | 110 ++ .../time_complexity.java | 167 +++ .../worst_best_time_complexity.java | 50 + .../binary_search_recur.java | 45 + .../build_tree.java | 51 + .../chapter_divide_and_conquer/hanota.java | 59 + .../climbing_stairs_backtrack.java | 44 + .../climbing_stairs_constraint_dp.java | 36 + .../climbing_stairs_dfs.java | 31 + .../climbing_stairs_dfs_mem.java | 41 + .../climbing_stairs_dp.java | 48 + .../coin_change.java | 72 ++ .../coin_change_ii.java | 67 + .../edit_distance.java | 139 +++ .../chapter_dynamic_programming/knapsack.java | 116 ++ .../min_cost_climbing_stairs_dp.java | 53 + .../min_path_sum.java | 125 ++ .../unbounded_knapsack.java | 63 + .../chapter_graph/graph_adjacency_list.java | 117 ++ .../chapter_graph/graph_adjacency_matrix.java | 131 ++ ja/codes/java/chapter_graph/graph_bfs.java | 55 + ja/codes/java/chapter_graph/graph_dfs.java | 51 + .../chapter_greedy/coin_change_greedy.java | 55 + .../chapter_greedy/fractional_knapsack.java | 59 + .../java/chapter_greedy/max_capacity.java | 38 + .../chapter_greedy/max_product_cutting.java | 40 + .../java/chapter_hashing/array_hash_map.java | 141 +++ .../java/chapter_hashing/built_in_hash.java | 38 + ja/codes/java/chapter_hashing/hash_map.java | 52 + .../chapter_hashing/hash_map_chaining.java | 148 +++ .../hash_map_open_addressing.java | 158 +++ .../java/chapter_hashing/simple_hash.java | 65 + ja/codes/java/chapter_heap/heap.java | 66 + ja/codes/java/chapter_heap/my_heap.java | 159 +++ ja/codes/java/chapter_heap/top_k.java | 40 + .../java/chapter_searching/binary_search.java | 58 + .../chapter_searching/binary_search_edge.java | 49 + .../binary_search_insertion.java | 63 + .../chapter_searching/hashing_search.java | 51 + .../java/chapter_searching/linear_search.java | 50 + ja/codes/java/chapter_searching/two_sum.java | 53 + .../java/chapter_sorting/bubble_sort.java | 57 + .../java/chapter_sorting/bucket_sort.java | 47 + .../java/chapter_sorting/counting_sort.java | 78 ++ ja/codes/java/chapter_sorting/heap_sort.java | 57 + .../java/chapter_sorting/insertion_sort.java | 31 + ja/codes/java/chapter_sorting/merge_sort.java | 58 + ja/codes/java/chapter_sorting/quick_sort.java | 158 +++ ja/codes/java/chapter_sorting/radix_sort.java | 69 + .../java/chapter_sorting/selection_sort.java | 35 + .../chapter_stack_and_queue/array_deque.java | 151 +++ .../chapter_stack_and_queue/array_queue.java | 114 ++ .../chapter_stack_and_queue/array_stack.java | 84 ++ .../java/chapter_stack_and_queue/deque.java | 46 + .../linkedlist_deque.java | 176 +++ .../linkedlist_queue.java | 104 ++ .../linkedlist_stack.java | 95 ++ .../java/chapter_stack_and_queue/queue.java | 40 + .../java/chapter_stack_and_queue/stack.java | 40 + .../java/chapter_tree/array_binary_tree.java | 136 ++ ja/codes/java/chapter_tree/avl_tree.java | 220 ++++ .../java/chapter_tree/binary_search_tree.java | 158 +++ ja/codes/java/chapter_tree/binary_tree.java | 40 + .../java/chapter_tree/binary_tree_bfs.java | 42 + .../java/chapter_tree/binary_tree_dfs.java | 68 + ja/codes/java/utils/ListNode.java | 28 + ja/codes/java/utils/PrintUtil.java | 116 ++ ja/codes/java/utils/TreeNode.java | 73 ++ ja/codes/java/utils/Vertex.java | 36 + .../chapter_array_and_linkedlist/array.py | 100 ++ .../linked_list.py | 85 ++ .../chapter_array_and_linkedlist/list.py | 56 + .../chapter_array_and_linkedlist/my_list.py | 118 ++ .../python/chapter_backtracking/n_queens.py | 62 + .../chapter_backtracking/permutations_i.py | 44 + .../chapter_backtracking/permutations_ii.py | 46 + .../preorder_traversal_i_compact.py | 36 + .../preorder_traversal_ii_compact.py | 42 + .../preorder_traversal_iii_compact.py | 43 + .../preorder_traversal_iii_template.py | 71 ++ .../chapter_backtracking/subset_sum_i.py | 48 + .../subset_sum_i_naive.py | 50 + .../chapter_backtracking/subset_sum_ii.py | 52 + .../iteration.py | 65 + .../recursion.py | 69 + .../space_complexity.py | 90 ++ .../time_complexity.py | 151 +++ .../worst_best_time_complexity.py | 36 + .../binary_search_recur.py | 40 + .../chapter_divide_and_conquer/build_tree.py | 54 + .../chapter_divide_and_conquer/hanota.py | 53 + .../climbing_stairs_backtrack.py | 37 + .../climbing_stairs_constraint_dp.py | 29 + .../climbing_stairs_dfs.py | 28 + .../climbing_stairs_dfs_mem.py | 35 + .../climbing_stairs_dp.py | 40 + .../coin_change.py | 60 + .../coin_change_ii.py | 58 + .../edit_distance.py | 123 ++ .../chapter_dynamic_programming/knapsack.py | 101 ++ .../min_cost_climbing_stairs_dp.py | 43 + .../min_path_sum.py | 104 ++ .../unbounded_knapsack.py | 55 + .../chapter_graph/graph_adjacency_list.py | 111 ++ .../chapter_graph/graph_adjacency_matrix.py | 116 ++ ja/codes/python/chapter_graph/graph_bfs.py | 64 + ja/codes/python/chapter_graph/graph_dfs.py | 57 + .../chapter_greedy/coin_change_greedy.py | 48 + .../chapter_greedy/fractional_knapsack.py | 46 + .../python/chapter_greedy/max_capacity.py | 33 + .../chapter_greedy/max_product_cutting.py | 33 + .../python/chapter_hashing/array_hash_map.py | 117 ++ .../python/chapter_hashing/built_in_hash.py | 37 + ja/codes/python/chapter_hashing/hash_map.py | 50 + .../chapter_hashing/hash_map_chaining.py | 118 ++ .../hash_map_open_addressing.py | 138 ++ .../python/chapter_hashing/simple_hash.py | 58 + ja/codes/python/chapter_heap/heap.py | 71 ++ ja/codes/python/chapter_heap/my_heap.py | 137 ++ ja/codes/python/chapter_heap/top_k.py | 39 + .../python/chapter_searching/binary_search.py | 52 + .../chapter_searching/binary_search_edge.py | 49 + .../binary_search_insertion.py | 54 + .../chapter_searching/hashing_search.py | 51 + .../python/chapter_searching/linear_search.py | 45 + ja/codes/python/chapter_searching/two_sum.py | 42 + .../python/chapter_sorting/bubble_sort.py | 44 + .../python/chapter_sorting/bucket_sort.py | 35 + .../python/chapter_sorting/counting_sort.py | 64 + ja/codes/python/chapter_sorting/heap_sort.py | 45 + .../python/chapter_sorting/insertion_sort.py | 25 + ja/codes/python/chapter_sorting/merge_sort.py | 55 + ja/codes/python/chapter_sorting/quick_sort.py | 129 ++ ja/codes/python/chapter_sorting/radix_sort.py | 69 + .../python/chapter_sorting/selection_sort.py | 26 + .../chapter_stack_and_queue/array_deque.py | 129 ++ .../chapter_stack_and_queue/array_queue.py | 98 ++ .../chapter_stack_and_queue/array_stack.py | 72 ++ .../python/chapter_stack_and_queue/deque.py | 42 + .../linkedlist_deque.py | 151 +++ .../linkedlist_queue.py | 97 ++ .../linkedlist_stack.py | 89 ++ .../python/chapter_stack_and_queue/queue.py | 39 + .../python/chapter_stack_and_queue/stack.py | 36 + .../python/chapter_tree/array_binary_tree.py | 119 ++ ja/codes/python/chapter_tree/avl_tree.py | 200 +++ .../python/chapter_tree/binary_search_tree.py | 146 +++ ja/codes/python/chapter_tree/binary_tree.py | 41 + .../python/chapter_tree/binary_tree_bfs.py | 42 + .../python/chapter_tree/binary_tree_dfs.py | 65 + ja/codes/python/modules/__init__.py | 19 + ja/codes/python/modules/list_node.py | 32 + ja/codes/python/modules/print_util.py | 81 ++ ja/codes/python/modules/tree_node.py | 69 + ja/codes/python/modules/vertex.py | 20 + ja/codes/python/test_all.py | 33 + ja/docs/assets/covers/chapter_appendix.jpg | Bin 0 -> 116562 bytes .../covers/chapter_array_and_linkedlist.jpg | Bin 0 -> 127660 bytes .../assets/covers/chapter_backtracking.jpg | Bin 0 -> 129893 bytes .../covers/chapter_complexity_analysis.jpg | Bin 0 -> 97602 bytes .../assets/covers/chapter_data_structure.jpg | Bin 0 -> 145130 bytes .../covers/chapter_divide_and_conquer.jpg | Bin 0 -> 105363 bytes .../covers/chapter_dynamic_programming.jpg | Bin 0 -> 166957 bytes ja/docs/assets/covers/chapter_graph.jpg | Bin 0 -> 86203 bytes ja/docs/assets/covers/chapter_greedy.jpg | Bin 0 -> 139099 bytes ja/docs/assets/covers/chapter_hashing.jpg | Bin 0 -> 139825 bytes ja/docs/assets/covers/chapter_heap.jpg | Bin 0 -> 112171 bytes ja/docs/assets/covers/chapter_hello_algo.jpg | Bin 0 -> 144989 bytes .../assets/covers/chapter_introduction.jpg | Bin 0 -> 140143 bytes ja/docs/assets/covers/chapter_preface.jpg | Bin 0 -> 121879 bytes ja/docs/assets/covers/chapter_searching.jpg | Bin 0 -> 131732 bytes ja/docs/assets/covers/chapter_sorting.jpg | Bin 0 -> 93281 bytes .../assets/covers/chapter_stack_and_queue.jpg | Bin 0 -> 106007 bytes ja/docs/assets/covers/chapter_tree.jpg | Bin 0 -> 120382 bytes .../contribution.assets/edit_markdown.png | Bin 0 -> 94973 bytes ja/docs/chapter_appendix/contribution.md | 47 + ja/docs/chapter_appendix/index.md | 3 + .../vscode_extension_installation.png | Bin 0 -> 125768 bytes .../vscode_installation.png | Bin 0 -> 105345 bytes ja/docs/chapter_appendix/installation.md | 68 + ja/docs/chapter_appendix/terminology.md | 137 ++ .../array.assets/array_definition.png | Bin 0 -> 20397 bytes .../array.assets/array_insert_element.png | Bin 0 -> 31118 bytes .../array_memory_location_calculation.png | Bin 0 -> 24641 bytes .../array.assets/array_remove_element.png | Bin 0 -> 30030 bytes ja/docs/chapter_array_and_linkedlist/array.md | 221 ++++ ja/docs/chapter_array_and_linkedlist/index.md | 9 + .../linkedlist_common_types.png | Bin 0 -> 17305 bytes .../linkedlist_definition.png | Bin 0 -> 26965 bytes .../linkedlist_insert_node.png | Bin 0 -> 20350 bytes .../linkedlist_remove_node.png | Bin 0 -> 24420 bytes .../linked_list.md | 686 ++++++++++ ja/docs/chapter_array_and_linkedlist/list.md | 906 ++++++++++++++ .../computer_storage_devices.png | Bin 0 -> 12041 bytes .../ram_and_cache.assets/storage_pyramid.png | Bin 0 -> 18722 bytes .../ram_and_cache.md | 71 ++ .../chapter_array_and_linkedlist/summary.md | 81 ++ .../backtrack_remove_return_or_not.png | Bin 0 -> 37306 bytes .../preorder_find_constrained_paths.png | Bin 0 -> 29712 bytes .../preorder_find_nodes.png | Bin 0 -> 30795 bytes .../preorder_find_paths_step1.png | Bin 0 -> 18355 bytes .../preorder_find_paths_step10.png | Bin 0 -> 30995 bytes .../preorder_find_paths_step11.png | Bin 0 -> 29489 bytes .../preorder_find_paths_step2.png | Bin 0 -> 22379 bytes .../preorder_find_paths_step3.png | Bin 0 -> 21897 bytes .../preorder_find_paths_step4.png | Bin 0 -> 22156 bytes .../preorder_find_paths_step5.png | Bin 0 -> 23658 bytes .../preorder_find_paths_step6.png | Bin 0 -> 24384 bytes .../preorder_find_paths_step7.png | Bin 0 -> 25291 bytes .../preorder_find_paths_step8.png | Bin 0 -> 26815 bytes .../preorder_find_paths_step9.png | Bin 0 -> 26863 bytes .../backtracking_algorithm.md | 509 ++++++++ ja/docs/chapter_backtracking/index.md | 9 + .../n_queens_cols_diagonals.png | Bin 0 -> 38776 bytes .../n_queens_constraints.png | Bin 0 -> 28721 bytes .../n_queens_placing.png | Bin 0 -> 27979 bytes .../solution_4_queens.png | Bin 0 -> 8235 bytes .../chapter_backtracking/n_queens_problem.md | 53 + .../permutations_i.png | Bin 0 -> 18342 bytes .../permutations_i_pruning.png | Bin 0 -> 29083 bytes .../permutations_ii.png | Bin 0 -> 21902 bytes .../permutations_ii_pruning.png | Bin 0 -> 26925 bytes .../permutations_ii_pruning_summary.png | Bin 0 -> 25119 bytes .../permutations_problem.md | 95 ++ .../subset_sum_i.png | Bin 0 -> 42245 bytes .../subset_sum_i_naive.png | Bin 0 -> 35417 bytes .../subset_sum_i_pruning.png | Bin 0 -> 35334 bytes .../subset_sum_ii.png | Bin 0 -> 41315 bytes .../subset_sum_ii_repeat.png | Bin 0 -> 21737 bytes .../subset_sum_problem.md | 95 ++ ja/docs/chapter_backtracking/summary.md | 23 + .../chapter_computational_complexity/index.md | 9 + .../iteration.png | Bin 0 -> 10847 bytes .../nested_iteration.png | Bin 0 -> 13650 bytes .../recursion_sum.png | Bin 0 -> 22910 bytes .../recursion_sum_depth.png | Bin 0 -> 24624 bytes .../recursion_tree.png | Bin 0 -> 15755 bytes .../tail_recursion_sum.png | Bin 0 -> 28024 bytes .../iteration_and_recursion.md | 194 +++ .../performance_evaluation.md | 49 + .../space_complexity_common_types.png | Bin 0 -> 20694 bytes .../space_complexity_exponential.png | Bin 0 -> 19691 bytes .../space_complexity_recursive_linear.png | Bin 0 -> 23930 bytes .../space_complexity_recursive_quadratic.png | Bin 0 -> 28307 bytes .../space_complexity.assets/space_types.png | Bin 0 -> 17114 bytes .../space_complexity.md | 803 ++++++++++++ .../summary.md | 49 + .../asymptotic_upper_bound.png | Bin 0 -> 22308 bytes .../time_complexity_common_types.png | Bin 0 -> 20994 bytes ...e_complexity_constant_linear_quadratic.png | Bin 0 -> 17903 bytes .../time_complexity_exponential.png | Bin 0 -> 20735 bytes .../time_complexity_factorial.png | Bin 0 -> 23290 bytes .../time_complexity_logarithmic.png | Bin 0 -> 21776 bytes .../time_complexity_logarithmic_linear.png | Bin 0 -> 23612 bytes .../time_complexity_simple_example.png | Bin 0 -> 12543 bytes .../time_complexity.md | 1112 +++++++++++++++++ .../basic_data_types.md | 170 +++ .../character_encoding.assets/ascii_table.png | Bin 0 -> 74094 bytes .../unicode_hello_algo.png | Bin 0 -> 18602 bytes .../utf-8_hello_algo.png | Bin 0 -> 23639 bytes .../character_encoding.md | 87 ++ .../classification_logic_structure.png | Bin 0 -> 27789 bytes .../classification_phisical_structure.png | Bin 0 -> 28224 bytes .../computer_memory_location.png | Bin 0 -> 75493 bytes .../classification_of_data_structure.md | 48 + ja/docs/chapter_data_structure/index.md | 9 + .../1s_2s_complement.png | Bin 0 -> 22682 bytes .../number_encoding.assets/ieee_754_float.png | Bin 0 -> 24201 bytes .../chapter_data_structure/number_encoding.md | 150 +++ ja/docs/chapter_data_structure/summary.md | 66 + .../binary_search_recur.png | Bin 0 -> 19036 bytes .../binary_search_recur.md | 45 + .../build_tree_division_pointers.png | Bin 0 -> 16926 bytes .../build_tree_example.png | Bin 0 -> 16791 bytes .../build_tree_preorder_inorder_division.png | Bin 0 -> 24936 bytes .../built_tree_overall.png | Bin 0 -> 29425 bytes .../built_tree_step1.png | Bin 0 -> 12411 bytes .../built_tree_step2.png | Bin 0 -> 13186 bytes .../built_tree_step3.png | Bin 0 -> 14154 bytes .../built_tree_step4.png | Bin 0 -> 15002 bytes .../built_tree_step5.png | Bin 0 -> 15311 bytes .../built_tree_step6.png | Bin 0 -> 16535 bytes .../built_tree_step7.png | Bin 0 -> 16799 bytes .../built_tree_step8.png | Bin 0 -> 18032 bytes .../built_tree_step9.png | Bin 0 -> 18518 bytes .../build_binary_tree_problem.md | 99 ++ .../divide_and_conquer_bubble_sort.png | Bin 0 -> 30681 bytes .../divide_and_conquer_merge_sort.png | Bin 0 -> 32206 bytes .../divide_and_conquer_parallel_computing.png | Bin 0 -> 18184 bytes .../divide_and_conquer.md | 91 ++ .../hanota_divide_and_conquer.png | Bin 0 -> 33566 bytes .../hanota_problem.assets/hanota_example.png | Bin 0 -> 11428 bytes .../hanota_problem.assets/hanota_f1_step1.png | Bin 0 -> 6734 bytes .../hanota_problem.assets/hanota_f1_step2.png | Bin 0 -> 10007 bytes .../hanota_problem.assets/hanota_f2_step1.png | Bin 0 -> 7203 bytes .../hanota_problem.assets/hanota_f2_step2.png | Bin 0 -> 10635 bytes .../hanota_problem.assets/hanota_f2_step3.png | Bin 0 -> 13448 bytes .../hanota_problem.assets/hanota_f2_step4.png | Bin 0 -> 16077 bytes .../hanota_problem.assets/hanota_f3_step1.png | Bin 0 -> 11392 bytes .../hanota_problem.assets/hanota_f3_step2.png | Bin 0 -> 11152 bytes .../hanota_problem.assets/hanota_f3_step3.png | Bin 0 -> 14260 bytes .../hanota_problem.assets/hanota_f3_step4.png | Bin 0 -> 16555 bytes .../hanota_recursive_tree.png | Bin 0 -> 19908 bytes .../hanota_problem.md | 97 ++ ja/docs/chapter_divide_and_conquer/index.md | 9 + ja/docs/chapter_divide_and_conquer/summary.md | 11 + .../climbing_stairs_constraint_example.png | Bin 0 -> 23540 bytes ...mbing_stairs_constraint_state_transfer.png | Bin 0 -> 21593 bytes .../min_cost_cs_dp.png | Bin 0 -> 22391 bytes .../min_cost_cs_example.png | Bin 0 -> 21113 bytes .../dp_problem_features.md | 101 ++ .../min_path_sum_dfs.png | Bin 0 -> 35044 bytes .../min_path_sum_dfs_mem.png | Bin 0 -> 25483 bytes .../min_path_sum_dp_step1.png | Bin 0 -> 12746 bytes .../min_path_sum_dp_step10.png | Bin 0 -> 16468 bytes .../min_path_sum_dp_step11.png | Bin 0 -> 16359 bytes .../min_path_sum_dp_step12.png | Bin 0 -> 14054 bytes .../min_path_sum_dp_step2.png | Bin 0 -> 19302 bytes .../min_path_sum_dp_step3.png | Bin 0 -> 16492 bytes .../min_path_sum_dp_step4.png | Bin 0 -> 16388 bytes .../min_path_sum_dp_step5.png | Bin 0 -> 16455 bytes .../min_path_sum_dp_step6.png | Bin 0 -> 16476 bytes .../min_path_sum_dp_step7.png | Bin 0 -> 16476 bytes .../min_path_sum_dp_step8.png | Bin 0 -> 16479 bytes .../min_path_sum_dp_step9.png | Bin 0 -> 16397 bytes .../min_path_sum_example.png | Bin 0 -> 16096 bytes .../min_path_sum_solution_initial_state.png | Bin 0 -> 25756 bytes ...min_path_sum_solution_state_definition.png | Bin 0 -> 27213 bytes ...min_path_sum_solution_state_transition.png | Bin 0 -> 20589 bytes .../dp_solution_pipeline.md | 183 +++ .../edit_distance_decision_tree.png | Bin 0 -> 24147 bytes .../edit_distance_dp_step1.png | Bin 0 -> 14164 bytes .../edit_distance_dp_step10.png | Bin 0 -> 15272 bytes .../edit_distance_dp_step11.png | Bin 0 -> 15071 bytes .../edit_distance_dp_step12.png | Bin 0 -> 15395 bytes .../edit_distance_dp_step13.png | Bin 0 -> 15398 bytes .../edit_distance_dp_step14.png | Bin 0 -> 15268 bytes .../edit_distance_dp_step15.png | Bin 0 -> 13003 bytes .../edit_distance_dp_step2.png | Bin 0 -> 14017 bytes .../edit_distance_dp_step3.png | Bin 0 -> 15084 bytes .../edit_distance_dp_step4.png | Bin 0 -> 15214 bytes .../edit_distance_dp_step5.png | Bin 0 -> 15187 bytes .../edit_distance_dp_step6.png | Bin 0 -> 15145 bytes .../edit_distance_dp_step7.png | Bin 0 -> 15248 bytes .../edit_distance_dp_step8.png | Bin 0 -> 14022 bytes .../edit_distance_dp_step9.png | Bin 0 -> 15237 bytes .../edit_distance_example.png | Bin 0 -> 13494 bytes .../edit_distance_state_transfer.png | Bin 0 -> 24326 bytes .../edit_distance_problem.md | 129 ++ ja/docs/chapter_dynamic_programming/index.md | 9 + .../climbing_stairs_dfs_memo_tree.png | Bin 0 -> 27164 bytes .../climbing_stairs_dfs_tree.png | Bin 0 -> 25070 bytes .../climbing_stairs_dp.png | Bin 0 -> 20459 bytes .../climbing_stairs_example.png | Bin 0 -> 17498 bytes .../climbing_stairs_state_transfer.png | Bin 0 -> 18986 bytes .../intro_to_dynamic_programming.md | 110 ++ .../knapsack_problem.assets/knapsack_dfs.png | Bin 0 -> 38014 bytes .../knapsack_dfs_mem.png | Bin 0 -> 38862 bytes .../knapsack_dp_comp_step1.png | Bin 0 -> 20505 bytes .../knapsack_dp_comp_step2.png | Bin 0 -> 22009 bytes .../knapsack_dp_comp_step3.png | Bin 0 -> 22306 bytes .../knapsack_dp_comp_step4.png | Bin 0 -> 22089 bytes .../knapsack_dp_comp_step5.png | Bin 0 -> 21593 bytes .../knapsack_dp_comp_step6.png | Bin 0 -> 21029 bytes .../knapsack_dp_step1.png | Bin 0 -> 19894 bytes .../knapsack_dp_step10.png | Bin 0 -> 19691 bytes .../knapsack_dp_step11.png | Bin 0 -> 19674 bytes .../knapsack_dp_step12.png | Bin 0 -> 20597 bytes .../knapsack_dp_step13.png | Bin 0 -> 20646 bytes .../knapsack_dp_step14.png | Bin 0 -> 18450 bytes .../knapsack_dp_step2.png | Bin 0 -> 20012 bytes .../knapsack_dp_step3.png | Bin 0 -> 20261 bytes .../knapsack_dp_step4.png | Bin 0 -> 20185 bytes .../knapsack_dp_step5.png | Bin 0 -> 20071 bytes .../knapsack_dp_step6.png | Bin 0 -> 19604 bytes .../knapsack_dp_step7.png | Bin 0 -> 20022 bytes .../knapsack_dp_step8.png | Bin 0 -> 20320 bytes .../knapsack_dp_step9.png | Bin 0 -> 20408 bytes .../knapsack_example.png | Bin 0 -> 23176 bytes .../knapsack_problem.md | 168 +++ .../chapter_dynamic_programming/summary.md | 23 + .../coin_change_dp_step1.png | Bin 0 -> 19784 bytes .../coin_change_dp_step10.png | Bin 0 -> 19919 bytes .../coin_change_dp_step11.png | Bin 0 -> 19489 bytes .../coin_change_dp_step12.png | Bin 0 -> 19738 bytes .../coin_change_dp_step13.png | Bin 0 -> 19902 bytes .../coin_change_dp_step14.png | Bin 0 -> 19858 bytes .../coin_change_dp_step15.png | Bin 0 -> 19503 bytes .../coin_change_dp_step2.png | Bin 0 -> 21183 bytes .../coin_change_dp_step3.png | Bin 0 -> 19840 bytes .../coin_change_dp_step4.png | Bin 0 -> 20077 bytes .../coin_change_dp_step5.png | Bin 0 -> 20245 bytes .../coin_change_dp_step6.png | Bin 0 -> 20109 bytes .../coin_change_dp_step7.png | Bin 0 -> 19596 bytes .../coin_change_dp_step8.png | Bin 0 -> 19810 bytes .../coin_change_dp_step9.png | Bin 0 -> 19961 bytes .../coin_change_example.png | Bin 0 -> 16605 bytes .../coin_change_ii_example.png | Bin 0 -> 19087 bytes .../unbounded_knapsack_dp_comp_step1.png | Bin 0 -> 21354 bytes .../unbounded_knapsack_dp_comp_step2.png | Bin 0 -> 21990 bytes .../unbounded_knapsack_dp_comp_step3.png | Bin 0 -> 22403 bytes .../unbounded_knapsack_dp_comp_step4.png | Bin 0 -> 22428 bytes .../unbounded_knapsack_dp_comp_step5.png | Bin 0 -> 22139 bytes .../unbounded_knapsack_dp_comp_step6.png | Bin 0 -> 21636 bytes .../unbounded_knapsack_example.png | Bin 0 -> 24771 bytes .../unbounded_knapsack_problem.md | 207 +++ .../graph.assets/adjacency_list.png | Bin 0 -> 21122 bytes .../graph.assets/adjacency_matrix.png | Bin 0 -> 19987 bytes .../graph.assets/connected_graph.png | Bin 0 -> 15771 bytes .../graph.assets/directed_graph.png | Bin 0 -> 17354 bytes .../graph.assets/linkedlist_tree_graph.png | Bin 0 -> 25975 bytes .../graph.assets/weighted_graph.png | Bin 0 -> 17587 bytes ja/docs/chapter_graph/graph.md | 83 ++ .../adjacency_list_step1_initialization.png | Bin 0 -> 23517 bytes .../adjacency_list_step2_add_edge.png | Bin 0 -> 25076 bytes .../adjacency_list_step3_remove_edge.png | Bin 0 -> 24398 bytes .../adjacency_list_step4_add_vertex.png | Bin 0 -> 25364 bytes .../adjacency_list_step5_remove_vertex.png | Bin 0 -> 22820 bytes .../adjacency_matrix_step1_initialization.png | Bin 0 -> 20746 bytes .../adjacency_matrix_step2_add_edge.png | Bin 0 -> 21584 bytes .../adjacency_matrix_step3_remove_edge.png | Bin 0 -> 21921 bytes .../adjacency_matrix_step4_add_vertex.png | Bin 0 -> 24916 bytes .../adjacency_matrix_step5_remove_vertex.png | Bin 0 -> 21398 bytes ja/docs/chapter_graph/graph_operations.md | 86 ++ .../graph_traversal.assets/graph_bfs.png | Bin 0 -> 27341 bytes .../graph_bfs_step1.png | Bin 0 -> 20645 bytes .../graph_bfs_step10.png | Bin 0 -> 39188 bytes .../graph_bfs_step11.png | Bin 0 -> 30829 bytes .../graph_bfs_step2.png | Bin 0 -> 32076 bytes .../graph_bfs_step3.png | Bin 0 -> 34599 bytes .../graph_bfs_step4.png | Bin 0 -> 35408 bytes .../graph_bfs_step5.png | Bin 0 -> 38045 bytes .../graph_bfs_step6.png | Bin 0 -> 38095 bytes .../graph_bfs_step7.png | Bin 0 -> 38116 bytes .../graph_bfs_step8.png | Bin 0 -> 39701 bytes .../graph_bfs_step9.png | Bin 0 -> 39150 bytes .../graph_traversal.assets/graph_dfs.png | Bin 0 -> 29814 bytes .../graph_dfs_step1.png | Bin 0 -> 17925 bytes .../graph_dfs_step10.png | Bin 0 -> 32011 bytes .../graph_dfs_step11.png | Bin 0 -> 35613 bytes .../graph_dfs_step2.png | Bin 0 -> 22945 bytes .../graph_dfs_step3.png | Bin 0 -> 24035 bytes .../graph_dfs_step4.png | Bin 0 -> 25075 bytes .../graph_dfs_step5.png | Bin 0 -> 25647 bytes .../graph_dfs_step6.png | Bin 0 -> 28303 bytes .../graph_dfs_step7.png | Bin 0 -> 26794 bytes .../graph_dfs_step8.png | Bin 0 -> 30052 bytes .../graph_dfs_step9.png | Bin 0 -> 36528 bytes ja/docs/chapter_graph/graph_traversal.md | 136 ++ ja/docs/chapter_graph/index.md | 9 + ja/docs/chapter_graph/summary.md | 31 + .../fractional_knapsack_area_chart.png | Bin 0 -> 13350 bytes .../fractional_knapsack_example.png | Bin 0 -> 28656 bytes .../fractional_knapsack_greedy_strategy.png | Bin 0 -> 22379 bytes .../fractional_knapsack_unit_value.png | Bin 0 -> 21155 bytes .../fractional_knapsack_problem.md | 50 + .../coin_change_greedy_strategy.png | Bin 0 -> 34652 bytes .../coin_change_greedy_vs_dp.png | Bin 0 -> 21344 bytes ja/docs/chapter_greedy/greedy_algorithm.md | 94 ++ ja/docs/chapter_greedy/index.md | 9 + .../max_capacity_example.png | Bin 0 -> 13192 bytes .../max_capacity_greedy_step1.png | Bin 0 -> 16061 bytes .../max_capacity_greedy_step2.png | Bin 0 -> 18520 bytes .../max_capacity_greedy_step3.png | Bin 0 -> 18895 bytes .../max_capacity_greedy_step4.png | Bin 0 -> 19184 bytes .../max_capacity_greedy_step5.png | Bin 0 -> 19283 bytes .../max_capacity_greedy_step6.png | Bin 0 -> 19644 bytes .../max_capacity_greedy_step7.png | Bin 0 -> 19679 bytes .../max_capacity_greedy_step8.png | Bin 0 -> 19848 bytes .../max_capacity_greedy_step9.png | Bin 0 -> 16470 bytes .../max_capacity_initial_state.png | Bin 0 -> 15847 bytes .../max_capacity_moving_long_board.png | Bin 0 -> 17782 bytes .../max_capacity_moving_short_board.png | Bin 0 -> 17507 bytes .../max_capacity_skipped_states.png | Bin 0 -> 24046 bytes .../chapter_greedy/max_capacity_problem.md | 99 ++ .../max_product_cutting_definition.png | Bin 0 -> 10689 bytes ...max_product_cutting_greedy_calculation.png | Bin 0 -> 7217 bytes .../max_product_cutting_greedy_infer1.png | Bin 0 -> 12170 bytes .../max_product_cutting_greedy_infer2.png | Bin 0 -> 10340 bytes .../max_product_cutting_problem.md | 85 ++ ja/docs/chapter_greedy/summary.md | 12 + .../hash_collision_best_worst_condition.png | Bin 0 -> 28674 bytes ja/docs/chapter_hashing/hash_algorithm.md | 362 ++++++ .../hash_table_chaining.png | Bin 0 -> 33666 bytes .../hash_table_linear_probing.png | Bin 0 -> 24540 bytes .../hash_table_open_addressing_deletion.png | Bin 0 -> 18192 bytes ja/docs/chapter_hashing/hash_collision.md | 108 ++ .../hash_map.assets/hash_collision.png | Bin 0 -> 28381 bytes .../hash_map.assets/hash_function.png | Bin 0 -> 31836 bytes .../hash_map.assets/hash_table_lookup.png | Bin 0 -> 19668 bytes .../hash_map.assets/hash_table_reshash.png | Bin 0 -> 29007 bytes ja/docs/chapter_hashing/hash_map.md | 529 ++++++++ ja/docs/chapter_hashing/index.md | 9 + ja/docs/chapter_hashing/summary.md | 47 + .../heapify_operations_count.png | Bin 0 -> 20426 bytes ja/docs/chapter_heap/build_heap.md | 74 ++ .../heap.assets/heap_pop_step1.png | Bin 0 -> 25391 bytes .../heap.assets/heap_pop_step10.png | Bin 0 -> 29889 bytes .../heap.assets/heap_pop_step2.png | Bin 0 -> 24814 bytes .../heap.assets/heap_pop_step3.png | Bin 0 -> 23121 bytes .../heap.assets/heap_pop_step4.png | Bin 0 -> 31038 bytes .../heap.assets/heap_pop_step5.png | Bin 0 -> 32806 bytes .../heap.assets/heap_pop_step6.png | Bin 0 -> 33069 bytes .../heap.assets/heap_pop_step7.png | Bin 0 -> 35037 bytes .../heap.assets/heap_pop_step8.png | Bin 0 -> 35161 bytes .../heap.assets/heap_pop_step9.png | Bin 0 -> 37614 bytes .../heap.assets/heap_push_step1.png | Bin 0 -> 21856 bytes .../heap.assets/heap_push_step2.png | Bin 0 -> 22154 bytes .../heap.assets/heap_push_step3.png | Bin 0 -> 28071 bytes .../heap.assets/heap_push_step4.png | Bin 0 -> 31378 bytes .../heap.assets/heap_push_step5.png | Bin 0 -> 29496 bytes .../heap.assets/heap_push_step6.png | Bin 0 -> 32145 bytes .../heap.assets/heap_push_step7.png | Bin 0 -> 29947 bytes .../heap.assets/heap_push_step8.png | Bin 0 -> 30979 bytes .../heap.assets/heap_push_step9.png | Bin 0 -> 28492 bytes .../heap.assets/min_heap_and_max_heap.png | Bin 0 -> 25673 bytes .../heap.assets/representation_of_heap.png | Bin 0 -> 39788 bytes ja/docs/chapter_heap/heap.md | 534 ++++++++ ja/docs/chapter_heap/index.md | 9 + ja/docs/chapter_heap/summary.md | 17 + .../top_k.assets/top_k_heap_step1.png | Bin 0 -> 16316 bytes .../top_k.assets/top_k_heap_step2.png | Bin 0 -> 17748 bytes .../top_k.assets/top_k_heap_step3.png | Bin 0 -> 19041 bytes .../top_k.assets/top_k_heap_step4.png | Bin 0 -> 23701 bytes .../top_k.assets/top_k_heap_step5.png | Bin 0 -> 25185 bytes .../top_k.assets/top_k_heap_step6.png | Bin 0 -> 28243 bytes .../top_k.assets/top_k_heap_step7.png | Bin 0 -> 23832 bytes .../top_k.assets/top_k_heap_step8.png | Bin 0 -> 24441 bytes .../top_k.assets/top_k_heap_step9.png | Bin 0 -> 18519 bytes .../top_k.assets/top_k_sorting.png | Bin 0 -> 10850 bytes .../top_k.assets/top_k_traversal.png | Bin 0 -> 22583 bytes ja/docs/chapter_heap/top_k.md | 73 ++ ja/docs/chapter_hello_algo/index.md | 30 + .../binary_search_dictionary_step1.png | Bin 0 -> 13409 bytes .../binary_search_dictionary_step2.png | Bin 0 -> 19742 bytes .../binary_search_dictionary_step3.png | Bin 0 -> 19761 bytes .../binary_search_dictionary_step4.png | Bin 0 -> 19359 bytes .../binary_search_dictionary_step5.png | Bin 0 -> 17360 bytes .../greedy_change.png | Bin 0 -> 27614 bytes .../playing_cards_sorting.png | Bin 0 -> 66790 bytes .../algorithms_are_everywhere.md | 56 + ja/docs/chapter_introduction/index.md | 9 + ja/docs/chapter_introduction/summary.md | 22 + .../what_is_dsa.assets/assembling_blocks.png | Bin 0 -> 362710 bytes ...p_between_data_structure_and_algorithm.png | Bin 0 -> 15601 bytes ja/docs/chapter_introduction/what_is_dsa.md | 53 + .../hello_algo_mindmap.png | Bin 0 -> 157698 bytes ja/docs/chapter_preface/about_the_book.md | 52 + ja/docs/chapter_preface/index.md | 9 + .../suggestions.assets/code_md_to_repo.png | Bin 0 -> 95179 bytes .../suggestions.assets/download_code.png | Bin 0 -> 93530 bytes .../suggestions.assets/learning_route.png | Bin 0 -> 26342 bytes .../pythontutor_example.png | Bin 0 -> 74149 bytes ja/docs/chapter_preface/suggestions.md | 239 ++++ ja/docs/chapter_preface/summary.md | 8 + ja/docs/chapter_reference/index.md | 25 + .../binary_search_example.png | Bin 0 -> 18584 bytes .../binary_search_ranges.png | Bin 0 -> 26810 bytes .../binary_search_step1.png | Bin 0 -> 18040 bytes .../binary_search_step2.png | Bin 0 -> 15823 bytes .../binary_search_step3.png | Bin 0 -> 19528 bytes .../binary_search_step4.png | Bin 0 -> 15228 bytes .../binary_search_step5.png | Bin 0 -> 19496 bytes .../binary_search_step6.png | Bin 0 -> 15204 bytes .../binary_search_step7.png | Bin 0 -> 16999 bytes ja/docs/chapter_searching/binary_search.md | 83 ++ .../binary_search_edge_by_element.png | Bin 0 -> 16829 bytes .../binary_search_right_edge_by_left_edge.png | Bin 0 -> 16077 bytes .../chapter_searching/binary_search_edge.md | 56 + .../binary_search_insertion_example.png | Bin 0 -> 18284 bytes .../binary_search_insertion_naive.png | Bin 0 -> 14062 bytes .../binary_search_insertion_step1.png | Bin 0 -> 17358 bytes .../binary_search_insertion_step2.png | Bin 0 -> 15224 bytes .../binary_search_insertion_step3.png | Bin 0 -> 19453 bytes .../binary_search_insertion_step4.png | Bin 0 -> 14891 bytes .../binary_search_insertion_step5.png | Bin 0 -> 19279 bytes .../binary_search_insertion_step6.png | Bin 0 -> 14720 bytes .../binary_search_insertion_step7.png | Bin 0 -> 18844 bytes .../binary_search_insertion_step8.png | Bin 0 -> 14042 bytes .../binary_search_insertion.md | 91 ++ ja/docs/chapter_searching/index.md | 9 + .../two_sum_brute_force.png | Bin 0 -> 13263 bytes .../two_sum_hashtable_step1.png | Bin 0 -> 15016 bytes .../two_sum_hashtable_step2.png | Bin 0 -> 15419 bytes .../two_sum_hashtable_step3.png | Bin 0 -> 16158 bytes .../replace_linear_by_hashing.md | 47 + .../searching_algorithms.png | Bin 0 -> 34491 bytes .../searching_algorithm_revisited.md | 84 ++ ja/docs/chapter_searching/summary.md | 8 + .../bubble_operation_step1.png | Bin 0 -> 14258 bytes .../bubble_operation_step2.png | Bin 0 -> 15244 bytes .../bubble_operation_step3.png | Bin 0 -> 15208 bytes .../bubble_operation_step4.png | Bin 0 -> 15209 bytes .../bubble_operation_step5.png | Bin 0 -> 12857 bytes .../bubble_operation_step6.png | Bin 0 -> 15300 bytes .../bubble_operation_step7.png | Bin 0 -> 14937 bytes .../bubble_sort_overview.png | Bin 0 -> 34384 bytes ja/docs/chapter_sorting/bubble_sort.md | 59 + .../bucket_sort_overview.png | Bin 0 -> 42395 bytes .../scatter_in_buckets_distribution.png | Bin 0 -> 19646 bytes .../scatter_in_buckets_recursively.png | Bin 0 -> 33795 bytes ja/docs/chapter_sorting/bucket_sort.md | 45 + .../counting_sort_overview.png | Bin 0 -> 29172 bytes .../counting_sort_step1.png | Bin 0 -> 22274 bytes .../counting_sort_step2.png | Bin 0 -> 21404 bytes .../counting_sort_step3.png | Bin 0 -> 24091 bytes .../counting_sort_step4.png | Bin 0 -> 24055 bytes .../counting_sort_step5.png | Bin 0 -> 24228 bytes .../counting_sort_step6.png | Bin 0 -> 24473 bytes .../counting_sort_step7.png | Bin 0 -> 20673 bytes .../counting_sort_step8.png | Bin 0 -> 16013 bytes ja/docs/chapter_sorting/counting_sort.md | 84 ++ .../heap_sort.assets/heap_sort_step1.png | Bin 0 -> 19068 bytes .../heap_sort.assets/heap_sort_step10.png | Bin 0 -> 20192 bytes .../heap_sort.assets/heap_sort_step11.png | Bin 0 -> 21790 bytes .../heap_sort.assets/heap_sort_step12.png | Bin 0 -> 23200 bytes .../heap_sort.assets/heap_sort_step2.png | Bin 0 -> 23116 bytes .../heap_sort.assets/heap_sort_step3.png | Bin 0 -> 24819 bytes .../heap_sort.assets/heap_sort_step4.png | Bin 0 -> 22808 bytes .../heap_sort.assets/heap_sort_step5.png | Bin 0 -> 24751 bytes .../heap_sort.assets/heap_sort_step6.png | Bin 0 -> 21749 bytes .../heap_sort.assets/heap_sort_step7.png | Bin 0 -> 23816 bytes .../heap_sort.assets/heap_sort_step8.png | Bin 0 -> 21043 bytes .../heap_sort.assets/heap_sort_step9.png | Bin 0 -> 23155 bytes ja/docs/chapter_sorting/heap_sort.md | 73 ++ ja/docs/chapter_sorting/index.md | 9 + .../insertion_operation.png | Bin 0 -> 28108 bytes .../insertion_sort_overview.png | Bin 0 -> 30392 bytes ja/docs/chapter_sorting/insertion_sort.md | 46 + .../merge_sort.assets/merge_sort_overview.png | Bin 0 -> 30774 bytes .../merge_sort.assets/merge_sort_step1.png | Bin 0 -> 10727 bytes .../merge_sort.assets/merge_sort_step10.png | Bin 0 -> 18008 bytes .../merge_sort.assets/merge_sort_step2.png | Bin 0 -> 11704 bytes .../merge_sort.assets/merge_sort_step3.png | Bin 0 -> 12314 bytes .../merge_sort.assets/merge_sort_step4.png | Bin 0 -> 12601 bytes .../merge_sort.assets/merge_sort_step5.png | Bin 0 -> 13768 bytes .../merge_sort.assets/merge_sort_step6.png | Bin 0 -> 14484 bytes .../merge_sort.assets/merge_sort_step7.png | Bin 0 -> 14774 bytes .../merge_sort.assets/merge_sort_step8.png | Bin 0 -> 15095 bytes .../merge_sort.assets/merge_sort_step9.png | Bin 0 -> 16055 bytes ja/docs/chapter_sorting/merge_sort.md | 73 ++ .../pivot_division_step1.png | Bin 0 -> 30615 bytes .../pivot_division_step2.png | Bin 0 -> 30725 bytes .../pivot_division_step3.png | Bin 0 -> 30788 bytes .../pivot_division_step4.png | Bin 0 -> 31470 bytes .../pivot_division_step5.png | Bin 0 -> 30747 bytes .../pivot_division_step6.png | Bin 0 -> 30875 bytes .../pivot_division_step7.png | Bin 0 -> 30605 bytes .../pivot_division_step8.png | Bin 0 -> 31675 bytes .../pivot_division_step9.png | Bin 0 -> 17217 bytes .../quick_sort.assets/quick_sort_overview.png | Bin 0 -> 28252 bytes ja/docs/chapter_sorting/quick_sort.md | 101 ++ .../radix_sort.assets/radix_sort_overview.png | Bin 0 -> 45191 bytes ja/docs/chapter_sorting/radix_sort.md | 41 + .../selection_sort_instability.png | Bin 0 -> 18411 bytes .../selection_sort_step1.png | Bin 0 -> 13086 bytes .../selection_sort_step10.png | Bin 0 -> 18020 bytes .../selection_sort_step11.png | Bin 0 -> 10744 bytes .../selection_sort_step2.png | Bin 0 -> 17904 bytes .../selection_sort_step3.png | Bin 0 -> 13322 bytes .../selection_sort_step4.png | Bin 0 -> 18154 bytes .../selection_sort_step5.png | Bin 0 -> 13346 bytes .../selection_sort_step6.png | Bin 0 -> 18390 bytes .../selection_sort_step7.png | Bin 0 -> 13222 bytes .../selection_sort_step8.png | Bin 0 -> 18196 bytes .../selection_sort_step9.png | Bin 0 -> 13161 bytes ja/docs/chapter_sorting/selection_sort.md | 58 + .../sorting_examples.png | Bin 0 -> 18536 bytes ja/docs/chapter_sorting/sorting_algorithm.md | 46 + .../sorting_algorithms_comparison.png | Bin 0 -> 59613 bytes ja/docs/chapter_sorting/summary.md | 47 + .../deque.assets/array_deque_step1.png | Bin 0 -> 26251 bytes .../array_deque_step2_push_last.png | Bin 0 -> 21335 bytes .../array_deque_step3_push_first.png | Bin 0 -> 20881 bytes .../array_deque_step4_pop_last.png | Bin 0 -> 16857 bytes .../array_deque_step5_pop_first.png | Bin 0 -> 18636 bytes .../deque.assets/deque_operations.png | Bin 0 -> 23181 bytes .../deque.assets/linkedlist_deque_step1.png | Bin 0 -> 18695 bytes .../linkedlist_deque_step2_push_last.png | Bin 0 -> 19200 bytes .../linkedlist_deque_step3_push_first.png | Bin 0 -> 20200 bytes .../linkedlist_deque_step4_pop_last.png | Bin 0 -> 17744 bytes .../linkedlist_deque_step5_pop_first.png | Bin 0 -> 16360 bytes ja/docs/chapter_stack_and_queue/deque.md | 401 ++++++ ja/docs/chapter_stack_and_queue/index.md | 9 + .../queue.assets/array_queue_step1.png | Bin 0 -> 25668 bytes .../queue.assets/array_queue_step2_push.png | Bin 0 -> 20462 bytes .../queue.assets/array_queue_step3_pop.png | Bin 0 -> 17790 bytes .../queue.assets/linkedlist_queue_step1.png | Bin 0 -> 18400 bytes .../linkedlist_queue_step2_push.png | Bin 0 -> 18567 bytes .../linkedlist_queue_step3_pop.png | Bin 0 -> 15944 bytes .../queue.assets/queue_operations.png | Bin 0 -> 20298 bytes ja/docs/chapter_stack_and_queue/queue.md | 377 ++++++ .../stack.assets/array_stack_step1.png | Bin 0 -> 17428 bytes .../stack.assets/array_stack_step2_push.png | Bin 0 -> 17120 bytes .../stack.assets/array_stack_step3_pop.png | Bin 0 -> 15421 bytes .../stack.assets/linkedlist_stack_step1.png | Bin 0 -> 16712 bytes .../linkedlist_stack_step2_push.png | Bin 0 -> 18723 bytes .../linkedlist_stack_step3_pop.png | Bin 0 -> 15913 bytes .../stack.assets/stack_operations.png | Bin 0 -> 18544 bytes ja/docs/chapter_stack_and_queue/stack.md | 385 ++++++ ja/docs/chapter_stack_and_queue/summary.md | 31 + .../array_representation_binary_tree.png | Bin 0 -> 32357 bytes ...ay_representation_complete_binary_tree.png | Bin 0 -> 23822 bytes .../array_representation_with_empty.png | Bin 0 -> 26353 bytes .../array_representation_without_empty.png | Bin 0 -> 22410 bytes .../array_representation_of_tree.md | 164 +++ ...vltree_degradation_from_inserting_node.png | Bin 0 -> 18783 bytes ...avltree_degradation_from_removing_node.png | Bin 0 -> 18633 bytes .../avltree_left_right_rotate.png | Bin 0 -> 22338 bytes .../avl_tree.assets/avltree_left_rotate.png | Bin 0 -> 22722 bytes .../avltree_left_rotate_with_grandchild.png | Bin 0 -> 27144 bytes .../avltree_right_left_rotate.png | Bin 0 -> 23235 bytes .../avltree_right_rotate_step1.png | Bin 0 -> 11652 bytes .../avltree_right_rotate_step2.png | Bin 0 -> 18478 bytes .../avltree_right_rotate_step3.png | Bin 0 -> 19237 bytes .../avltree_right_rotate_step4.png | Bin 0 -> 23725 bytes .../avltree_right_rotate_with_grandchild.png | Bin 0 -> 27422 bytes .../avltree_rotation_cases.png | Bin 0 -> 24063 bytes ja/docs/chapter_tree/avl_tree.md | 353 ++++++ .../binary_search_tree.png | Bin 0 -> 12915 bytes .../bst_degradation.png | Bin 0 -> 19981 bytes .../bst_inorder_traversal.png | Bin 0 -> 35107 bytes .../binary_search_tree.assets/bst_insert.png | Bin 0 -> 26894 bytes .../bst_remove_case1.png | Bin 0 -> 28023 bytes .../bst_remove_case2.png | Bin 0 -> 28433 bytes .../bst_remove_case3_step1.png | Bin 0 -> 17481 bytes .../bst_remove_case3_step2.png | Bin 0 -> 27878 bytes .../bst_remove_case3_step3.png | Bin 0 -> 29060 bytes .../bst_remove_case3_step4.png | Bin 0 -> 31754 bytes .../bst_search_step1.png | Bin 0 -> 16215 bytes .../bst_search_step2.png | Bin 0 -> 16623 bytes .../bst_search_step3.png | Bin 0 -> 17751 bytes .../bst_search_step4.png | Bin 0 -> 18850 bytes ja/docs/chapter_tree/binary_search_tree.md | 129 ++ .../balanced_binary_tree.png | Bin 0 -> 18507 bytes .../binary_tree_add_remove.png | Bin 0 -> 22011 bytes .../binary_tree_best_worst_cases.png | Bin 0 -> 21040 bytes .../binary_tree_definition.png | Bin 0 -> 26327 bytes .../binary_tree_terminology.png | Bin 0 -> 23252 bytes .../complete_binary_tree.png | Bin 0 -> 16086 bytes .../binary_tree.assets/full_binary_tree.png | Bin 0 -> 10884 bytes .../perfect_binary_tree.png | Bin 0 -> 13350 bytes ja/docs/chapter_tree/binary_tree.md | 654 ++++++++++ .../binary_tree_bfs.png | Bin 0 -> 17527 bytes .../binary_tree_dfs.png | Bin 0 -> 48857 bytes .../preorder_step1.png | Bin 0 -> 16979 bytes .../preorder_step10.png | Bin 0 -> 26647 bytes .../preorder_step11.png | Bin 0 -> 28266 bytes .../preorder_step2.png | Bin 0 -> 18481 bytes .../preorder_step3.png | Bin 0 -> 19507 bytes .../preorder_step4.png | Bin 0 -> 20285 bytes .../preorder_step5.png | Bin 0 -> 21654 bytes .../preorder_step6.png | Bin 0 -> 23054 bytes .../preorder_step7.png | Bin 0 -> 24249 bytes .../preorder_step8.png | Bin 0 -> 25128 bytes .../preorder_step9.png | Bin 0 -> 25559 bytes ja/docs/chapter_tree/binary_tree_traversal.md | 89 ++ ja/docs/chapter_tree/index.md | 9 + ja/docs/chapter_tree/summary.md | 54 + ja/docs/index.assets/animation.gif | Bin 0 -> 119276 bytes ja/docs/index.assets/animation_dark.gif | Bin 0 -> 123228 bytes ja/docs/index.assets/btn_chinese_edition.svg | 1 + .../index.assets/btn_chinese_edition_dark.svg | 1 + ja/docs/index.assets/btn_download_pdf.svg | 1 + .../index.assets/btn_download_pdf_dark.svg | 1 + ja/docs/index.assets/btn_read_online.svg | 1 + ja/docs/index.assets/btn_read_online_dark.svg | 1 + ja/docs/index.assets/comment.gif | Bin 0 -> 320242 bytes ja/docs/index.assets/hello_algo_header.png | Bin 0 -> 140430 bytes .../index.assets/hello_algo_mindmap_tp.png | Bin 0 -> 29153 bytes ja/docs/index.assets/running_code.gif | Bin 0 -> 255319 bytes ja/docs/index.assets/running_code_dark.gif | Bin 0 -> 253661 bytes ja/docs/index.html | 369 ++++++ ja/docs/index.md | 5 + ja/mkdocs.yml | 180 +++ mkdocs.yml | 3 + overrides/main.html | 2 + zh-hant/README.md | 2 + 886 files changed, 33569 insertions(+) create mode 100644 docs/assets/avatar/avatar_eltociear.jpg create mode 100644 ja/CONTRIBUTING.md create mode 100644 ja/README.md create mode 100644 ja/codes/cpp/chapter_array_and_linkedlist/array.cpp create mode 100644 ja/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp create mode 100644 ja/codes/cpp/chapter_array_and_linkedlist/list.cpp create mode 100644 ja/codes/cpp/chapter_array_and_linkedlist/my_list.cpp create mode 100644 ja/codes/cpp/chapter_backtracking/n_queens.cpp create mode 100644 ja/codes/cpp/chapter_backtracking/permutations_i.cpp create mode 100644 ja/codes/cpp/chapter_backtracking/permutations_ii.cpp create mode 100644 ja/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp create mode 100644 ja/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp create mode 100644 ja/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp create mode 100644 ja/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp create mode 100644 ja/codes/cpp/chapter_backtracking/subset_sum_i.cpp create mode 100644 ja/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp create mode 100644 ja/codes/cpp/chapter_backtracking/subset_sum_ii.cpp create mode 100644 ja/codes/cpp/chapter_computational_complexity/iteration.cpp create mode 100644 ja/codes/cpp/chapter_computational_complexity/recursion.cpp create mode 100644 ja/codes/cpp/chapter_computational_complexity/space_complexity.cpp create mode 100644 ja/codes/cpp/chapter_computational_complexity/time_complexity.cpp create mode 100644 ja/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp create mode 100644 ja/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp create mode 100644 ja/codes/cpp/chapter_divide_and_conquer/build_tree.cpp create mode 100644 ja/codes/cpp/chapter_divide_and_conquer/hanota.cpp create mode 100644 ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp create mode 100644 ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp create mode 100644 ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp create mode 100644 ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp create mode 100644 ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp create mode 100644 ja/codes/cpp/chapter_dynamic_programming/coin_change.cpp create mode 100644 ja/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp create mode 100644 ja/codes/cpp/chapter_dynamic_programming/edit_distance.cpp create mode 100644 ja/codes/cpp/chapter_dynamic_programming/knapsack.cpp create mode 100644 ja/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp create mode 100644 ja/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp create mode 100644 ja/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp create mode 100644 ja/codes/cpp/chapter_graph/graph_adjacency_list.cpp create mode 100644 ja/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp create mode 100644 ja/codes/cpp/chapter_graph/graph_bfs.cpp create mode 100644 ja/codes/cpp/chapter_graph/graph_dfs.cpp create mode 100644 ja/codes/cpp/chapter_greedy/coin_change_greedy.cpp create mode 100644 ja/codes/cpp/chapter_greedy/fractional_knapsack.cpp create mode 100644 ja/codes/cpp/chapter_greedy/max_capacity.cpp create mode 100644 ja/codes/cpp/chapter_greedy/max_product_cutting.cpp create mode 100644 ja/codes/cpp/chapter_hashing/array_hash_map.cpp create mode 100644 ja/codes/cpp/chapter_hashing/array_hash_map_test.cpp create mode 100644 ja/codes/cpp/chapter_hashing/built_in_hash.cpp create mode 100644 ja/codes/cpp/chapter_hashing/hash_map.cpp create mode 100644 ja/codes/cpp/chapter_hashing/hash_map_chaining.cpp create mode 100644 ja/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp create mode 100644 ja/codes/cpp/chapter_hashing/simple_hash.cpp create mode 100644 ja/codes/cpp/chapter_heap/heap.cpp create mode 100644 ja/codes/cpp/chapter_heap/my_heap.cpp create mode 100644 ja/codes/cpp/chapter_heap/top_k.cpp create mode 100644 ja/codes/cpp/chapter_searching/binary_search.cpp create mode 100644 ja/codes/cpp/chapter_searching/binary_search_edge.cpp create mode 100644 ja/codes/cpp/chapter_searching/binary_search_insertion.cpp create mode 100644 ja/codes/cpp/chapter_searching/hashing_search.cpp create mode 100644 ja/codes/cpp/chapter_searching/linear_search.cpp create mode 100644 ja/codes/cpp/chapter_searching/two_sum.cpp create mode 100644 ja/codes/cpp/chapter_sorting/bubble_sort.cpp create mode 100644 ja/codes/cpp/chapter_sorting/bucket_sort.cpp create mode 100644 ja/codes/cpp/chapter_sorting/counting_sort.cpp create mode 100644 ja/codes/cpp/chapter_sorting/heap_sort.cpp create mode 100644 ja/codes/cpp/chapter_sorting/insertion_sort.cpp create mode 100644 ja/codes/cpp/chapter_sorting/merge_sort.cpp create mode 100644 ja/codes/cpp/chapter_sorting/quick_sort.cpp create mode 100644 ja/codes/cpp/chapter_sorting/radix_sort.cpp create mode 100644 ja/codes/cpp/chapter_sorting/selection_sort.cpp create mode 100644 ja/codes/cpp/chapter_stack_and_queue/array_deque.cpp create mode 100644 ja/codes/cpp/chapter_stack_and_queue/array_queue.cpp create mode 100644 ja/codes/cpp/chapter_stack_and_queue/array_stack.cpp create mode 100644 ja/codes/cpp/chapter_stack_and_queue/deque.cpp create mode 100644 ja/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp create mode 100644 ja/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp create mode 100644 ja/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp create mode 100644 ja/codes/cpp/chapter_stack_and_queue/queue.cpp create mode 100644 ja/codes/cpp/chapter_stack_and_queue/stack.cpp create mode 100644 ja/codes/cpp/chapter_tree/array_binary_tree.cpp create mode 100644 ja/codes/cpp/chapter_tree/avl_tree.cpp create mode 100644 ja/codes/cpp/chapter_tree/binary_search_tree.cpp create mode 100644 ja/codes/cpp/chapter_tree/binary_tree.cpp create mode 100644 ja/codes/cpp/chapter_tree/binary_tree_bfs.cpp create mode 100644 ja/codes/cpp/chapter_tree/binary_tree_dfs.cpp create mode 100644 ja/codes/cpp/utils/common.hpp create mode 100644 ja/codes/cpp/utils/list_node.hpp create mode 100644 ja/codes/cpp/utils/print_utils.hpp create mode 100644 ja/codes/cpp/utils/tree_node.hpp create mode 100644 ja/codes/cpp/utils/vertex.hpp create mode 100644 ja/codes/java/chapter_array_and_linkedlist/array.java create mode 100644 ja/codes/java/chapter_array_and_linkedlist/linked_list.java create mode 100644 ja/codes/java/chapter_array_and_linkedlist/list.java create mode 100644 ja/codes/java/chapter_array_and_linkedlist/my_list.java create mode 100644 ja/codes/java/chapter_backtracking/n_queens.java create mode 100644 ja/codes/java/chapter_backtracking/permutations_i.java create mode 100644 ja/codes/java/chapter_backtracking/permutations_ii.java create mode 100644 ja/codes/java/chapter_backtracking/preorder_traversal_i_compact.java create mode 100644 ja/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java create mode 100644 ja/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java create mode 100644 ja/codes/java/chapter_backtracking/preorder_traversal_iii_template.java create mode 100644 ja/codes/java/chapter_backtracking/subset_sum_i.java create mode 100644 ja/codes/java/chapter_backtracking/subset_sum_i_naive.java create mode 100644 ja/codes/java/chapter_backtracking/subset_sum_ii.java create mode 100644 ja/codes/java/chapter_computational_complexity/iteration.java create mode 100644 ja/codes/java/chapter_computational_complexity/recursion.java create mode 100644 ja/codes/java/chapter_computational_complexity/space_complexity.java create mode 100644 ja/codes/java/chapter_computational_complexity/time_complexity.java create mode 100644 ja/codes/java/chapter_computational_complexity/worst_best_time_complexity.java create mode 100644 ja/codes/java/chapter_divide_and_conquer/binary_search_recur.java create mode 100644 ja/codes/java/chapter_divide_and_conquer/build_tree.java create mode 100644 ja/codes/java/chapter_divide_and_conquer/hanota.java create mode 100644 ja/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java create mode 100644 ja/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java create mode 100644 ja/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java create mode 100644 ja/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java create mode 100644 ja/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java create mode 100644 ja/codes/java/chapter_dynamic_programming/coin_change.java create mode 100644 ja/codes/java/chapter_dynamic_programming/coin_change_ii.java create mode 100644 ja/codes/java/chapter_dynamic_programming/edit_distance.java create mode 100644 ja/codes/java/chapter_dynamic_programming/knapsack.java create mode 100644 ja/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java create mode 100644 ja/codes/java/chapter_dynamic_programming/min_path_sum.java create mode 100644 ja/codes/java/chapter_dynamic_programming/unbounded_knapsack.java create mode 100644 ja/codes/java/chapter_graph/graph_adjacency_list.java create mode 100644 ja/codes/java/chapter_graph/graph_adjacency_matrix.java create mode 100644 ja/codes/java/chapter_graph/graph_bfs.java create mode 100644 ja/codes/java/chapter_graph/graph_dfs.java create mode 100644 ja/codes/java/chapter_greedy/coin_change_greedy.java create mode 100644 ja/codes/java/chapter_greedy/fractional_knapsack.java create mode 100644 ja/codes/java/chapter_greedy/max_capacity.java create mode 100644 ja/codes/java/chapter_greedy/max_product_cutting.java create mode 100644 ja/codes/java/chapter_hashing/array_hash_map.java create mode 100644 ja/codes/java/chapter_hashing/built_in_hash.java create mode 100644 ja/codes/java/chapter_hashing/hash_map.java create mode 100644 ja/codes/java/chapter_hashing/hash_map_chaining.java create mode 100644 ja/codes/java/chapter_hashing/hash_map_open_addressing.java create mode 100644 ja/codes/java/chapter_hashing/simple_hash.java create mode 100644 ja/codes/java/chapter_heap/heap.java create mode 100644 ja/codes/java/chapter_heap/my_heap.java create mode 100644 ja/codes/java/chapter_heap/top_k.java create mode 100644 ja/codes/java/chapter_searching/binary_search.java create mode 100644 ja/codes/java/chapter_searching/binary_search_edge.java create mode 100644 ja/codes/java/chapter_searching/binary_search_insertion.java create mode 100644 ja/codes/java/chapter_searching/hashing_search.java create mode 100644 ja/codes/java/chapter_searching/linear_search.java create mode 100644 ja/codes/java/chapter_searching/two_sum.java create mode 100644 ja/codes/java/chapter_sorting/bubble_sort.java create mode 100644 ja/codes/java/chapter_sorting/bucket_sort.java create mode 100644 ja/codes/java/chapter_sorting/counting_sort.java create mode 100644 ja/codes/java/chapter_sorting/heap_sort.java create mode 100644 ja/codes/java/chapter_sorting/insertion_sort.java create mode 100644 ja/codes/java/chapter_sorting/merge_sort.java create mode 100644 ja/codes/java/chapter_sorting/quick_sort.java create mode 100644 ja/codes/java/chapter_sorting/radix_sort.java create mode 100644 ja/codes/java/chapter_sorting/selection_sort.java create mode 100644 ja/codes/java/chapter_stack_and_queue/array_deque.java create mode 100644 ja/codes/java/chapter_stack_and_queue/array_queue.java create mode 100644 ja/codes/java/chapter_stack_and_queue/array_stack.java create mode 100644 ja/codes/java/chapter_stack_and_queue/deque.java create mode 100644 ja/codes/java/chapter_stack_and_queue/linkedlist_deque.java create mode 100644 ja/codes/java/chapter_stack_and_queue/linkedlist_queue.java create mode 100644 ja/codes/java/chapter_stack_and_queue/linkedlist_stack.java create mode 100644 ja/codes/java/chapter_stack_and_queue/queue.java create mode 100644 ja/codes/java/chapter_stack_and_queue/stack.java create mode 100644 ja/codes/java/chapter_tree/array_binary_tree.java create mode 100644 ja/codes/java/chapter_tree/avl_tree.java create mode 100644 ja/codes/java/chapter_tree/binary_search_tree.java create mode 100644 ja/codes/java/chapter_tree/binary_tree.java create mode 100644 ja/codes/java/chapter_tree/binary_tree_bfs.java create mode 100644 ja/codes/java/chapter_tree/binary_tree_dfs.java create mode 100644 ja/codes/java/utils/ListNode.java create mode 100644 ja/codes/java/utils/PrintUtil.java create mode 100644 ja/codes/java/utils/TreeNode.java create mode 100644 ja/codes/java/utils/Vertex.java create mode 100644 ja/codes/python/chapter_array_and_linkedlist/array.py create mode 100644 ja/codes/python/chapter_array_and_linkedlist/linked_list.py create mode 100644 ja/codes/python/chapter_array_and_linkedlist/list.py create mode 100644 ja/codes/python/chapter_array_and_linkedlist/my_list.py create mode 100644 ja/codes/python/chapter_backtracking/n_queens.py create mode 100644 ja/codes/python/chapter_backtracking/permutations_i.py create mode 100644 ja/codes/python/chapter_backtracking/permutations_ii.py create mode 100644 ja/codes/python/chapter_backtracking/preorder_traversal_i_compact.py create mode 100644 ja/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py create mode 100644 ja/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py create mode 100644 ja/codes/python/chapter_backtracking/preorder_traversal_iii_template.py create mode 100644 ja/codes/python/chapter_backtracking/subset_sum_i.py create mode 100644 ja/codes/python/chapter_backtracking/subset_sum_i_naive.py create mode 100644 ja/codes/python/chapter_backtracking/subset_sum_ii.py create mode 100644 ja/codes/python/chapter_computational_complexity/iteration.py create mode 100644 ja/codes/python/chapter_computational_complexity/recursion.py create mode 100644 ja/codes/python/chapter_computational_complexity/space_complexity.py create mode 100644 ja/codes/python/chapter_computational_complexity/time_complexity.py create mode 100644 ja/codes/python/chapter_computational_complexity/worst_best_time_complexity.py create mode 100644 ja/codes/python/chapter_divide_and_conquer/binary_search_recur.py create mode 100644 ja/codes/python/chapter_divide_and_conquer/build_tree.py create mode 100644 ja/codes/python/chapter_divide_and_conquer/hanota.py create mode 100644 ja/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py create mode 100644 ja/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py create mode 100644 ja/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py create mode 100644 ja/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py create mode 100644 ja/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py create mode 100644 ja/codes/python/chapter_dynamic_programming/coin_change.py create mode 100644 ja/codes/python/chapter_dynamic_programming/coin_change_ii.py create mode 100644 ja/codes/python/chapter_dynamic_programming/edit_distance.py create mode 100644 ja/codes/python/chapter_dynamic_programming/knapsack.py create mode 100644 ja/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py create mode 100644 ja/codes/python/chapter_dynamic_programming/min_path_sum.py create mode 100644 ja/codes/python/chapter_dynamic_programming/unbounded_knapsack.py create mode 100644 ja/codes/python/chapter_graph/graph_adjacency_list.py create mode 100644 ja/codes/python/chapter_graph/graph_adjacency_matrix.py create mode 100644 ja/codes/python/chapter_graph/graph_bfs.py create mode 100644 ja/codes/python/chapter_graph/graph_dfs.py create mode 100644 ja/codes/python/chapter_greedy/coin_change_greedy.py create mode 100644 ja/codes/python/chapter_greedy/fractional_knapsack.py create mode 100644 ja/codes/python/chapter_greedy/max_capacity.py create mode 100644 ja/codes/python/chapter_greedy/max_product_cutting.py create mode 100644 ja/codes/python/chapter_hashing/array_hash_map.py create mode 100644 ja/codes/python/chapter_hashing/built_in_hash.py create mode 100644 ja/codes/python/chapter_hashing/hash_map.py create mode 100644 ja/codes/python/chapter_hashing/hash_map_chaining.py create mode 100644 ja/codes/python/chapter_hashing/hash_map_open_addressing.py create mode 100644 ja/codes/python/chapter_hashing/simple_hash.py create mode 100644 ja/codes/python/chapter_heap/heap.py create mode 100644 ja/codes/python/chapter_heap/my_heap.py create mode 100644 ja/codes/python/chapter_heap/top_k.py create mode 100644 ja/codes/python/chapter_searching/binary_search.py create mode 100644 ja/codes/python/chapter_searching/binary_search_edge.py create mode 100644 ja/codes/python/chapter_searching/binary_search_insertion.py create mode 100644 ja/codes/python/chapter_searching/hashing_search.py create mode 100644 ja/codes/python/chapter_searching/linear_search.py create mode 100644 ja/codes/python/chapter_searching/two_sum.py create mode 100644 ja/codes/python/chapter_sorting/bubble_sort.py create mode 100644 ja/codes/python/chapter_sorting/bucket_sort.py create mode 100644 ja/codes/python/chapter_sorting/counting_sort.py create mode 100644 ja/codes/python/chapter_sorting/heap_sort.py create mode 100644 ja/codes/python/chapter_sorting/insertion_sort.py create mode 100644 ja/codes/python/chapter_sorting/merge_sort.py create mode 100644 ja/codes/python/chapter_sorting/quick_sort.py create mode 100644 ja/codes/python/chapter_sorting/radix_sort.py create mode 100644 ja/codes/python/chapter_sorting/selection_sort.py create mode 100644 ja/codes/python/chapter_stack_and_queue/array_deque.py create mode 100644 ja/codes/python/chapter_stack_and_queue/array_queue.py create mode 100644 ja/codes/python/chapter_stack_and_queue/array_stack.py create mode 100644 ja/codes/python/chapter_stack_and_queue/deque.py create mode 100644 ja/codes/python/chapter_stack_and_queue/linkedlist_deque.py create mode 100644 ja/codes/python/chapter_stack_and_queue/linkedlist_queue.py create mode 100644 ja/codes/python/chapter_stack_and_queue/linkedlist_stack.py create mode 100644 ja/codes/python/chapter_stack_and_queue/queue.py create mode 100644 ja/codes/python/chapter_stack_and_queue/stack.py create mode 100644 ja/codes/python/chapter_tree/array_binary_tree.py create mode 100644 ja/codes/python/chapter_tree/avl_tree.py create mode 100644 ja/codes/python/chapter_tree/binary_search_tree.py create mode 100644 ja/codes/python/chapter_tree/binary_tree.py create mode 100644 ja/codes/python/chapter_tree/binary_tree_bfs.py create mode 100644 ja/codes/python/chapter_tree/binary_tree_dfs.py create mode 100644 ja/codes/python/modules/__init__.py create mode 100644 ja/codes/python/modules/list_node.py create mode 100644 ja/codes/python/modules/print_util.py create mode 100644 ja/codes/python/modules/tree_node.py create mode 100644 ja/codes/python/modules/vertex.py create mode 100644 ja/codes/python/test_all.py create mode 100644 ja/docs/assets/covers/chapter_appendix.jpg create mode 100644 ja/docs/assets/covers/chapter_array_and_linkedlist.jpg create mode 100644 ja/docs/assets/covers/chapter_backtracking.jpg create mode 100644 ja/docs/assets/covers/chapter_complexity_analysis.jpg create mode 100644 ja/docs/assets/covers/chapter_data_structure.jpg create mode 100644 ja/docs/assets/covers/chapter_divide_and_conquer.jpg create mode 100644 ja/docs/assets/covers/chapter_dynamic_programming.jpg create mode 100644 ja/docs/assets/covers/chapter_graph.jpg create mode 100644 ja/docs/assets/covers/chapter_greedy.jpg create mode 100644 ja/docs/assets/covers/chapter_hashing.jpg create mode 100644 ja/docs/assets/covers/chapter_heap.jpg create mode 100644 ja/docs/assets/covers/chapter_hello_algo.jpg create mode 100644 ja/docs/assets/covers/chapter_introduction.jpg create mode 100644 ja/docs/assets/covers/chapter_preface.jpg create mode 100644 ja/docs/assets/covers/chapter_searching.jpg create mode 100644 ja/docs/assets/covers/chapter_sorting.jpg create mode 100644 ja/docs/assets/covers/chapter_stack_and_queue.jpg create mode 100644 ja/docs/assets/covers/chapter_tree.jpg create mode 100644 ja/docs/chapter_appendix/contribution.assets/edit_markdown.png create mode 100644 ja/docs/chapter_appendix/contribution.md create mode 100644 ja/docs/chapter_appendix/index.md create mode 100644 ja/docs/chapter_appendix/installation.assets/vscode_extension_installation.png create mode 100644 ja/docs/chapter_appendix/installation.assets/vscode_installation.png create mode 100644 ja/docs/chapter_appendix/installation.md create mode 100644 ja/docs/chapter_appendix/terminology.md create mode 100644 ja/docs/chapter_array_and_linkedlist/array.assets/array_definition.png create mode 100644 ja/docs/chapter_array_and_linkedlist/array.assets/array_insert_element.png create mode 100644 ja/docs/chapter_array_and_linkedlist/array.assets/array_memory_location_calculation.png create mode 100644 ja/docs/chapter_array_and_linkedlist/array.assets/array_remove_element.png create mode 100644 ja/docs/chapter_array_and_linkedlist/array.md create mode 100644 ja/docs/chapter_array_and_linkedlist/index.md create mode 100644 ja/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_common_types.png create mode 100644 ja/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_definition.png create mode 100644 ja/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_insert_node.png create mode 100644 ja/docs/chapter_array_and_linkedlist/linked_list.assets/linkedlist_remove_node.png create mode 100644 ja/docs/chapter_array_and_linkedlist/linked_list.md create mode 100644 ja/docs/chapter_array_and_linkedlist/list.md create mode 100644 ja/docs/chapter_array_and_linkedlist/ram_and_cache.assets/computer_storage_devices.png create mode 100644 ja/docs/chapter_array_and_linkedlist/ram_and_cache.assets/storage_pyramid.png create mode 100644 ja/docs/chapter_array_and_linkedlist/ram_and_cache.md create mode 100644 ja/docs/chapter_array_and_linkedlist/summary.md create mode 100644 ja/docs/chapter_backtracking/backtracking_algorithm.assets/backtrack_remove_return_or_not.png create mode 100644 ja/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_constrained_paths.png create mode 100644 ja/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_nodes.png create mode 100644 ja/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step1.png create mode 100644 ja/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step10.png create mode 100644 ja/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step11.png create mode 100644 ja/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step2.png create mode 100644 ja/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step3.png create mode 100644 ja/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step4.png create mode 100644 ja/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step5.png create mode 100644 ja/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step6.png create mode 100644 ja/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step7.png create mode 100644 ja/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step8.png create mode 100644 ja/docs/chapter_backtracking/backtracking_algorithm.assets/preorder_find_paths_step9.png create mode 100644 ja/docs/chapter_backtracking/backtracking_algorithm.md create mode 100644 ja/docs/chapter_backtracking/index.md create mode 100644 ja/docs/chapter_backtracking/n_queens_problem.assets/n_queens_cols_diagonals.png create mode 100644 ja/docs/chapter_backtracking/n_queens_problem.assets/n_queens_constraints.png create mode 100644 ja/docs/chapter_backtracking/n_queens_problem.assets/n_queens_placing.png create mode 100644 ja/docs/chapter_backtracking/n_queens_problem.assets/solution_4_queens.png create mode 100644 ja/docs/chapter_backtracking/n_queens_problem.md create mode 100644 ja/docs/chapter_backtracking/permutations_problem.assets/permutations_i.png create mode 100644 ja/docs/chapter_backtracking/permutations_problem.assets/permutations_i_pruning.png create mode 100644 ja/docs/chapter_backtracking/permutations_problem.assets/permutations_ii.png create mode 100644 ja/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning.png create mode 100644 ja/docs/chapter_backtracking/permutations_problem.assets/permutations_ii_pruning_summary.png create mode 100644 ja/docs/chapter_backtracking/permutations_problem.md create mode 100644 ja/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i.png create mode 100644 ja/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_naive.png create mode 100644 ja/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_i_pruning.png create mode 100644 ja/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii.png create mode 100644 ja/docs/chapter_backtracking/subset_sum_problem.assets/subset_sum_ii_repeat.png create mode 100644 ja/docs/chapter_backtracking/subset_sum_problem.md create mode 100644 ja/docs/chapter_backtracking/summary.md create mode 100644 ja/docs/chapter_computational_complexity/index.md create mode 100644 ja/docs/chapter_computational_complexity/iteration_and_recursion.assets/iteration.png create mode 100644 ja/docs/chapter_computational_complexity/iteration_and_recursion.assets/nested_iteration.png create mode 100644 ja/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum.png create mode 100644 ja/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_sum_depth.png create mode 100644 ja/docs/chapter_computational_complexity/iteration_and_recursion.assets/recursion_tree.png create mode 100644 ja/docs/chapter_computational_complexity/iteration_and_recursion.assets/tail_recursion_sum.png create mode 100644 ja/docs/chapter_computational_complexity/iteration_and_recursion.md create mode 100644 ja/docs/chapter_computational_complexity/performance_evaluation.md create mode 100644 ja/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_common_types.png create mode 100644 ja/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_exponential.png create mode 100644 ja/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_linear.png create mode 100644 ja/docs/chapter_computational_complexity/space_complexity.assets/space_complexity_recursive_quadratic.png create mode 100644 ja/docs/chapter_computational_complexity/space_complexity.assets/space_types.png create mode 100644 ja/docs/chapter_computational_complexity/space_complexity.md create mode 100644 ja/docs/chapter_computational_complexity/summary.md create mode 100644 ja/docs/chapter_computational_complexity/time_complexity.assets/asymptotic_upper_bound.png create mode 100644 ja/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_common_types.png create mode 100644 ja/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_constant_linear_quadratic.png create mode 100644 ja/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_exponential.png create mode 100644 ja/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_factorial.png create mode 100644 ja/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic.png create mode 100644 ja/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_logarithmic_linear.png create mode 100644 ja/docs/chapter_computational_complexity/time_complexity.assets/time_complexity_simple_example.png create mode 100644 ja/docs/chapter_computational_complexity/time_complexity.md create mode 100644 ja/docs/chapter_data_structure/basic_data_types.md create mode 100644 ja/docs/chapter_data_structure/character_encoding.assets/ascii_table.png create mode 100644 ja/docs/chapter_data_structure/character_encoding.assets/unicode_hello_algo.png create mode 100644 ja/docs/chapter_data_structure/character_encoding.assets/utf-8_hello_algo.png create mode 100644 ja/docs/chapter_data_structure/character_encoding.md create mode 100644 ja/docs/chapter_data_structure/classification_of_data_structure.assets/classification_logic_structure.png create mode 100644 ja/docs/chapter_data_structure/classification_of_data_structure.assets/classification_phisical_structure.png create mode 100644 ja/docs/chapter_data_structure/classification_of_data_structure.assets/computer_memory_location.png create mode 100644 ja/docs/chapter_data_structure/classification_of_data_structure.md create mode 100644 ja/docs/chapter_data_structure/index.md create mode 100644 ja/docs/chapter_data_structure/number_encoding.assets/1s_2s_complement.png create mode 100644 ja/docs/chapter_data_structure/number_encoding.assets/ieee_754_float.png create mode 100644 ja/docs/chapter_data_structure/number_encoding.md create mode 100644 ja/docs/chapter_data_structure/summary.md create mode 100644 ja/docs/chapter_divide_and_conquer/binary_search_recur.assets/binary_search_recur.png create mode 100644 ja/docs/chapter_divide_and_conquer/binary_search_recur.md create mode 100644 ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_division_pointers.png create mode 100644 ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_example.png create mode 100644 ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png create mode 100644 ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_overall.png create mode 100644 ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step1.png create mode 100644 ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step2.png create mode 100644 ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step3.png create mode 100644 ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step4.png create mode 100644 ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step5.png create mode 100644 ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step6.png create mode 100644 ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step7.png create mode 100644 ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step8.png create mode 100644 ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.assets/built_tree_step9.png create mode 100644 ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.md create mode 100644 ja/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_bubble_sort.png create mode 100644 ja/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_merge_sort.png create mode 100644 ja/docs/chapter_divide_and_conquer/divide_and_conquer.assets/divide_and_conquer_parallel_computing.png create mode 100644 ja/docs/chapter_divide_and_conquer/divide_and_conquer.md create mode 100644 ja/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_divide_and_conquer.png create mode 100644 ja/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_example.png create mode 100644 ja/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step1.png create mode 100644 ja/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f1_step2.png create mode 100644 ja/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step1.png create mode 100644 ja/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step2.png create mode 100644 ja/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step3.png create mode 100644 ja/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f2_step4.png create mode 100644 ja/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step1.png create mode 100644 ja/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step2.png create mode 100644 ja/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step3.png create mode 100644 ja/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_f3_step4.png create mode 100644 ja/docs/chapter_divide_and_conquer/hanota_problem.assets/hanota_recursive_tree.png create mode 100644 ja/docs/chapter_divide_and_conquer/hanota_problem.md create mode 100644 ja/docs/chapter_divide_and_conquer/index.md create mode 100644 ja/docs/chapter_divide_and_conquer/summary.md create mode 100644 ja/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_example.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_dp.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_problem_features.assets/min_cost_cs_example.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_problem_features.md create mode 100644 ja/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dfs_mem.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step1.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step10.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step11.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step12.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step2.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step3.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step4.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step5.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step6.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step7.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step8.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_dp_step9.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_example.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_definition.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_solution_pipeline.assets/min_path_sum_solution_state_transition.png create mode 100644 ja/docs/chapter_dynamic_programming/dp_solution_pipeline.md create mode 100644 ja/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_decision_tree.png create mode 100644 ja/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step1.png create mode 100644 ja/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step10.png create mode 100644 ja/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step11.png create mode 100644 ja/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step12.png create mode 100644 ja/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step13.png create mode 100644 ja/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step14.png create mode 100644 ja/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step15.png create mode 100644 ja/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step2.png create mode 100644 ja/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step3.png create mode 100644 ja/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step4.png create mode 100644 ja/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step5.png create mode 100644 ja/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step6.png create mode 100644 ja/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step7.png create mode 100644 ja/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step8.png create mode 100644 ja/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_dp_step9.png create mode 100644 ja/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_example.png create mode 100644 ja/docs/chapter_dynamic_programming/edit_distance_problem.assets/edit_distance_state_transfer.png create mode 100644 ja/docs/chapter_dynamic_programming/edit_distance_problem.md create mode 100644 ja/docs/chapter_dynamic_programming/index.md create mode 100644 ja/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png create mode 100644 ja/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png create mode 100644 ja/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_dp.png create mode 100644 ja/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_example.png create mode 100644 ja/docs/chapter_dynamic_programming/intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png create mode 100644 ja/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dfs_mem.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step1.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step2.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step3.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step4.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step5.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_comp_step6.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step1.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step10.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step11.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step12.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step13.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step14.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step2.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step3.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step4.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step5.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step6.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step7.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step8.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_dp_step9.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.assets/knapsack_example.png create mode 100644 ja/docs/chapter_dynamic_programming/knapsack_problem.md create mode 100644 ja/docs/chapter_dynamic_programming/summary.md create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step1.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step10.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step11.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step12.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step13.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step14.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step15.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step2.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step3.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step4.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step5.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step6.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step7.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step8.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_dp_step9.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_example.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/coin_change_ii_example.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.assets/unbounded_knapsack_example.png create mode 100644 ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md create mode 100644 ja/docs/chapter_graph/graph.assets/adjacency_list.png create mode 100644 ja/docs/chapter_graph/graph.assets/adjacency_matrix.png create mode 100644 ja/docs/chapter_graph/graph.assets/connected_graph.png create mode 100644 ja/docs/chapter_graph/graph.assets/directed_graph.png create mode 100644 ja/docs/chapter_graph/graph.assets/linkedlist_tree_graph.png create mode 100644 ja/docs/chapter_graph/graph.assets/weighted_graph.png create mode 100644 ja/docs/chapter_graph/graph.md create mode 100644 ja/docs/chapter_graph/graph_operations.assets/adjacency_list_step1_initialization.png create mode 100644 ja/docs/chapter_graph/graph_operations.assets/adjacency_list_step2_add_edge.png create mode 100644 ja/docs/chapter_graph/graph_operations.assets/adjacency_list_step3_remove_edge.png create mode 100644 ja/docs/chapter_graph/graph_operations.assets/adjacency_list_step4_add_vertex.png create mode 100644 ja/docs/chapter_graph/graph_operations.assets/adjacency_list_step5_remove_vertex.png create mode 100644 ja/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step1_initialization.png create mode 100644 ja/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step2_add_edge.png create mode 100644 ja/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step3_remove_edge.png create mode 100644 ja/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step4_add_vertex.png create mode 100644 ja/docs/chapter_graph/graph_operations.assets/adjacency_matrix_step5_remove_vertex.png create mode 100644 ja/docs/chapter_graph/graph_operations.md create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_bfs.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_bfs_step1.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_bfs_step10.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_bfs_step11.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_bfs_step2.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_bfs_step3.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_bfs_step4.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_bfs_step5.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_bfs_step6.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_bfs_step7.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_bfs_step8.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_bfs_step9.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_dfs.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_dfs_step1.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_dfs_step10.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_dfs_step11.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_dfs_step2.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_dfs_step3.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_dfs_step4.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_dfs_step5.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_dfs_step6.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_dfs_step7.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_dfs_step8.png create mode 100644 ja/docs/chapter_graph/graph_traversal.assets/graph_dfs_step9.png create mode 100644 ja/docs/chapter_graph/graph_traversal.md create mode 100644 ja/docs/chapter_graph/index.md create mode 100644 ja/docs/chapter_graph/summary.md create mode 100644 ja/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png create mode 100644 ja/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_example.png create mode 100644 ja/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png create mode 100644 ja/docs/chapter_greedy/fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png create mode 100644 ja/docs/chapter_greedy/fractional_knapsack_problem.md create mode 100644 ja/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_strategy.png create mode 100644 ja/docs/chapter_greedy/greedy_algorithm.assets/coin_change_greedy_vs_dp.png create mode 100644 ja/docs/chapter_greedy/greedy_algorithm.md create mode 100644 ja/docs/chapter_greedy/index.md create mode 100644 ja/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_example.png create mode 100644 ja/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step1.png create mode 100644 ja/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step2.png create mode 100644 ja/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step3.png create mode 100644 ja/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step4.png create mode 100644 ja/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step5.png create mode 100644 ja/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step6.png create mode 100644 ja/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step7.png create mode 100644 ja/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step8.png create mode 100644 ja/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_greedy_step9.png create mode 100644 ja/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_initial_state.png create mode 100644 ja/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_long_board.png create mode 100644 ja/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_moving_short_board.png create mode 100644 ja/docs/chapter_greedy/max_capacity_problem.assets/max_capacity_skipped_states.png create mode 100644 ja/docs/chapter_greedy/max_capacity_problem.md create mode 100644 ja/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_definition.png create mode 100644 ja/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png create mode 100644 ja/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png create mode 100644 ja/docs/chapter_greedy/max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png create mode 100644 ja/docs/chapter_greedy/max_product_cutting_problem.md create mode 100644 ja/docs/chapter_greedy/summary.md create mode 100644 ja/docs/chapter_hashing/hash_algorithm.assets/hash_collision_best_worst_condition.png create mode 100644 ja/docs/chapter_hashing/hash_algorithm.md create mode 100644 ja/docs/chapter_hashing/hash_collision.assets/hash_table_chaining.png create mode 100644 ja/docs/chapter_hashing/hash_collision.assets/hash_table_linear_probing.png create mode 100644 ja/docs/chapter_hashing/hash_collision.assets/hash_table_open_addressing_deletion.png create mode 100644 ja/docs/chapter_hashing/hash_collision.md create mode 100644 ja/docs/chapter_hashing/hash_map.assets/hash_collision.png create mode 100644 ja/docs/chapter_hashing/hash_map.assets/hash_function.png create mode 100644 ja/docs/chapter_hashing/hash_map.assets/hash_table_lookup.png create mode 100644 ja/docs/chapter_hashing/hash_map.assets/hash_table_reshash.png create mode 100644 ja/docs/chapter_hashing/hash_map.md create mode 100644 ja/docs/chapter_hashing/index.md create mode 100644 ja/docs/chapter_hashing/summary.md create mode 100644 ja/docs/chapter_heap/build_heap.assets/heapify_operations_count.png create mode 100644 ja/docs/chapter_heap/build_heap.md create mode 100644 ja/docs/chapter_heap/heap.assets/heap_pop_step1.png create mode 100644 ja/docs/chapter_heap/heap.assets/heap_pop_step10.png create mode 100644 ja/docs/chapter_heap/heap.assets/heap_pop_step2.png create mode 100644 ja/docs/chapter_heap/heap.assets/heap_pop_step3.png create mode 100644 ja/docs/chapter_heap/heap.assets/heap_pop_step4.png create mode 100644 ja/docs/chapter_heap/heap.assets/heap_pop_step5.png create mode 100644 ja/docs/chapter_heap/heap.assets/heap_pop_step6.png create mode 100644 ja/docs/chapter_heap/heap.assets/heap_pop_step7.png create mode 100644 ja/docs/chapter_heap/heap.assets/heap_pop_step8.png create mode 100644 ja/docs/chapter_heap/heap.assets/heap_pop_step9.png create mode 100644 ja/docs/chapter_heap/heap.assets/heap_push_step1.png create mode 100644 ja/docs/chapter_heap/heap.assets/heap_push_step2.png create mode 100644 ja/docs/chapter_heap/heap.assets/heap_push_step3.png create mode 100644 ja/docs/chapter_heap/heap.assets/heap_push_step4.png create mode 100644 ja/docs/chapter_heap/heap.assets/heap_push_step5.png create mode 100644 ja/docs/chapter_heap/heap.assets/heap_push_step6.png create mode 100644 ja/docs/chapter_heap/heap.assets/heap_push_step7.png create mode 100644 ja/docs/chapter_heap/heap.assets/heap_push_step8.png create mode 100644 ja/docs/chapter_heap/heap.assets/heap_push_step9.png create mode 100644 ja/docs/chapter_heap/heap.assets/min_heap_and_max_heap.png create mode 100644 ja/docs/chapter_heap/heap.assets/representation_of_heap.png create mode 100644 ja/docs/chapter_heap/heap.md create mode 100644 ja/docs/chapter_heap/index.md create mode 100644 ja/docs/chapter_heap/summary.md create mode 100644 ja/docs/chapter_heap/top_k.assets/top_k_heap_step1.png create mode 100644 ja/docs/chapter_heap/top_k.assets/top_k_heap_step2.png create mode 100644 ja/docs/chapter_heap/top_k.assets/top_k_heap_step3.png create mode 100644 ja/docs/chapter_heap/top_k.assets/top_k_heap_step4.png create mode 100644 ja/docs/chapter_heap/top_k.assets/top_k_heap_step5.png create mode 100644 ja/docs/chapter_heap/top_k.assets/top_k_heap_step6.png create mode 100644 ja/docs/chapter_heap/top_k.assets/top_k_heap_step7.png create mode 100644 ja/docs/chapter_heap/top_k.assets/top_k_heap_step8.png create mode 100644 ja/docs/chapter_heap/top_k.assets/top_k_heap_step9.png create mode 100644 ja/docs/chapter_heap/top_k.assets/top_k_sorting.png create mode 100644 ja/docs/chapter_heap/top_k.assets/top_k_traversal.png create mode 100644 ja/docs/chapter_heap/top_k.md create mode 100644 ja/docs/chapter_hello_algo/index.md create mode 100644 ja/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step1.png create mode 100644 ja/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step2.png create mode 100644 ja/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step3.png create mode 100644 ja/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step4.png create mode 100644 ja/docs/chapter_introduction/algorithms_are_everywhere.assets/binary_search_dictionary_step5.png create mode 100644 ja/docs/chapter_introduction/algorithms_are_everywhere.assets/greedy_change.png create mode 100644 ja/docs/chapter_introduction/algorithms_are_everywhere.assets/playing_cards_sorting.png create mode 100644 ja/docs/chapter_introduction/algorithms_are_everywhere.md create mode 100644 ja/docs/chapter_introduction/index.md create mode 100644 ja/docs/chapter_introduction/summary.md create mode 100644 ja/docs/chapter_introduction/what_is_dsa.assets/assembling_blocks.png create mode 100644 ja/docs/chapter_introduction/what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png create mode 100644 ja/docs/chapter_introduction/what_is_dsa.md create mode 100644 ja/docs/chapter_preface/about_the_book.assets/hello_algo_mindmap.png create mode 100644 ja/docs/chapter_preface/about_the_book.md create mode 100644 ja/docs/chapter_preface/index.md create mode 100644 ja/docs/chapter_preface/suggestions.assets/code_md_to_repo.png create mode 100644 ja/docs/chapter_preface/suggestions.assets/download_code.png create mode 100644 ja/docs/chapter_preface/suggestions.assets/learning_route.png create mode 100644 ja/docs/chapter_preface/suggestions.assets/pythontutor_example.png create mode 100644 ja/docs/chapter_preface/suggestions.md create mode 100644 ja/docs/chapter_preface/summary.md create mode 100644 ja/docs/chapter_reference/index.md create mode 100644 ja/docs/chapter_searching/binary_search.assets/binary_search_example.png create mode 100644 ja/docs/chapter_searching/binary_search.assets/binary_search_ranges.png create mode 100644 ja/docs/chapter_searching/binary_search.assets/binary_search_step1.png create mode 100644 ja/docs/chapter_searching/binary_search.assets/binary_search_step2.png create mode 100644 ja/docs/chapter_searching/binary_search.assets/binary_search_step3.png create mode 100644 ja/docs/chapter_searching/binary_search.assets/binary_search_step4.png create mode 100644 ja/docs/chapter_searching/binary_search.assets/binary_search_step5.png create mode 100644 ja/docs/chapter_searching/binary_search.assets/binary_search_step6.png create mode 100644 ja/docs/chapter_searching/binary_search.assets/binary_search_step7.png create mode 100644 ja/docs/chapter_searching/binary_search.md create mode 100644 ja/docs/chapter_searching/binary_search_edge.assets/binary_search_edge_by_element.png create mode 100644 ja/docs/chapter_searching/binary_search_edge.assets/binary_search_right_edge_by_left_edge.png create mode 100644 ja/docs/chapter_searching/binary_search_edge.md create mode 100644 ja/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_example.png create mode 100644 ja/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_naive.png create mode 100644 ja/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step1.png create mode 100644 ja/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step2.png create mode 100644 ja/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step3.png create mode 100644 ja/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step4.png create mode 100644 ja/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step5.png create mode 100644 ja/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step6.png create mode 100644 ja/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step7.png create mode 100644 ja/docs/chapter_searching/binary_search_insertion.assets/binary_search_insertion_step8.png create mode 100644 ja/docs/chapter_searching/binary_search_insertion.md create mode 100644 ja/docs/chapter_searching/index.md create mode 100644 ja/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_brute_force.png create mode 100644 ja/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step1.png create mode 100644 ja/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step2.png create mode 100644 ja/docs/chapter_searching/replace_linear_by_hashing.assets/two_sum_hashtable_step3.png create mode 100644 ja/docs/chapter_searching/replace_linear_by_hashing.md create mode 100644 ja/docs/chapter_searching/searching_algorithm_revisited.assets/searching_algorithms.png create mode 100644 ja/docs/chapter_searching/searching_algorithm_revisited.md create mode 100644 ja/docs/chapter_searching/summary.md create mode 100644 ja/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step1.png create mode 100644 ja/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step2.png create mode 100644 ja/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step3.png create mode 100644 ja/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step4.png create mode 100644 ja/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step5.png create mode 100644 ja/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step6.png create mode 100644 ja/docs/chapter_sorting/bubble_sort.assets/bubble_operation_step7.png create mode 100644 ja/docs/chapter_sorting/bubble_sort.assets/bubble_sort_overview.png create mode 100644 ja/docs/chapter_sorting/bubble_sort.md create mode 100644 ja/docs/chapter_sorting/bucket_sort.assets/bucket_sort_overview.png create mode 100644 ja/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_distribution.png create mode 100644 ja/docs/chapter_sorting/bucket_sort.assets/scatter_in_buckets_recursively.png create mode 100644 ja/docs/chapter_sorting/bucket_sort.md create mode 100644 ja/docs/chapter_sorting/counting_sort.assets/counting_sort_overview.png create mode 100644 ja/docs/chapter_sorting/counting_sort.assets/counting_sort_step1.png create mode 100644 ja/docs/chapter_sorting/counting_sort.assets/counting_sort_step2.png create mode 100644 ja/docs/chapter_sorting/counting_sort.assets/counting_sort_step3.png create mode 100644 ja/docs/chapter_sorting/counting_sort.assets/counting_sort_step4.png create mode 100644 ja/docs/chapter_sorting/counting_sort.assets/counting_sort_step5.png create mode 100644 ja/docs/chapter_sorting/counting_sort.assets/counting_sort_step6.png create mode 100644 ja/docs/chapter_sorting/counting_sort.assets/counting_sort_step7.png create mode 100644 ja/docs/chapter_sorting/counting_sort.assets/counting_sort_step8.png create mode 100644 ja/docs/chapter_sorting/counting_sort.md create mode 100644 ja/docs/chapter_sorting/heap_sort.assets/heap_sort_step1.png create mode 100644 ja/docs/chapter_sorting/heap_sort.assets/heap_sort_step10.png create mode 100644 ja/docs/chapter_sorting/heap_sort.assets/heap_sort_step11.png create mode 100644 ja/docs/chapter_sorting/heap_sort.assets/heap_sort_step12.png create mode 100644 ja/docs/chapter_sorting/heap_sort.assets/heap_sort_step2.png create mode 100644 ja/docs/chapter_sorting/heap_sort.assets/heap_sort_step3.png create mode 100644 ja/docs/chapter_sorting/heap_sort.assets/heap_sort_step4.png create mode 100644 ja/docs/chapter_sorting/heap_sort.assets/heap_sort_step5.png create mode 100644 ja/docs/chapter_sorting/heap_sort.assets/heap_sort_step6.png create mode 100644 ja/docs/chapter_sorting/heap_sort.assets/heap_sort_step7.png create mode 100644 ja/docs/chapter_sorting/heap_sort.assets/heap_sort_step8.png create mode 100644 ja/docs/chapter_sorting/heap_sort.assets/heap_sort_step9.png create mode 100644 ja/docs/chapter_sorting/heap_sort.md create mode 100644 ja/docs/chapter_sorting/index.md create mode 100644 ja/docs/chapter_sorting/insertion_sort.assets/insertion_operation.png create mode 100644 ja/docs/chapter_sorting/insertion_sort.assets/insertion_sort_overview.png create mode 100644 ja/docs/chapter_sorting/insertion_sort.md create mode 100644 ja/docs/chapter_sorting/merge_sort.assets/merge_sort_overview.png create mode 100644 ja/docs/chapter_sorting/merge_sort.assets/merge_sort_step1.png create mode 100644 ja/docs/chapter_sorting/merge_sort.assets/merge_sort_step10.png create mode 100644 ja/docs/chapter_sorting/merge_sort.assets/merge_sort_step2.png create mode 100644 ja/docs/chapter_sorting/merge_sort.assets/merge_sort_step3.png create mode 100644 ja/docs/chapter_sorting/merge_sort.assets/merge_sort_step4.png create mode 100644 ja/docs/chapter_sorting/merge_sort.assets/merge_sort_step5.png create mode 100644 ja/docs/chapter_sorting/merge_sort.assets/merge_sort_step6.png create mode 100644 ja/docs/chapter_sorting/merge_sort.assets/merge_sort_step7.png create mode 100644 ja/docs/chapter_sorting/merge_sort.assets/merge_sort_step8.png create mode 100644 ja/docs/chapter_sorting/merge_sort.assets/merge_sort_step9.png create mode 100644 ja/docs/chapter_sorting/merge_sort.md create mode 100644 ja/docs/chapter_sorting/quick_sort.assets/pivot_division_step1.png create mode 100644 ja/docs/chapter_sorting/quick_sort.assets/pivot_division_step2.png create mode 100644 ja/docs/chapter_sorting/quick_sort.assets/pivot_division_step3.png create mode 100644 ja/docs/chapter_sorting/quick_sort.assets/pivot_division_step4.png create mode 100644 ja/docs/chapter_sorting/quick_sort.assets/pivot_division_step5.png create mode 100644 ja/docs/chapter_sorting/quick_sort.assets/pivot_division_step6.png create mode 100644 ja/docs/chapter_sorting/quick_sort.assets/pivot_division_step7.png create mode 100644 ja/docs/chapter_sorting/quick_sort.assets/pivot_division_step8.png create mode 100644 ja/docs/chapter_sorting/quick_sort.assets/pivot_division_step9.png create mode 100644 ja/docs/chapter_sorting/quick_sort.assets/quick_sort_overview.png create mode 100644 ja/docs/chapter_sorting/quick_sort.md create mode 100644 ja/docs/chapter_sorting/radix_sort.assets/radix_sort_overview.png create mode 100644 ja/docs/chapter_sorting/radix_sort.md create mode 100644 ja/docs/chapter_sorting/selection_sort.assets/selection_sort_instability.png create mode 100644 ja/docs/chapter_sorting/selection_sort.assets/selection_sort_step1.png create mode 100644 ja/docs/chapter_sorting/selection_sort.assets/selection_sort_step10.png create mode 100644 ja/docs/chapter_sorting/selection_sort.assets/selection_sort_step11.png create mode 100644 ja/docs/chapter_sorting/selection_sort.assets/selection_sort_step2.png create mode 100644 ja/docs/chapter_sorting/selection_sort.assets/selection_sort_step3.png create mode 100644 ja/docs/chapter_sorting/selection_sort.assets/selection_sort_step4.png create mode 100644 ja/docs/chapter_sorting/selection_sort.assets/selection_sort_step5.png create mode 100644 ja/docs/chapter_sorting/selection_sort.assets/selection_sort_step6.png create mode 100644 ja/docs/chapter_sorting/selection_sort.assets/selection_sort_step7.png create mode 100644 ja/docs/chapter_sorting/selection_sort.assets/selection_sort_step8.png create mode 100644 ja/docs/chapter_sorting/selection_sort.assets/selection_sort_step9.png create mode 100644 ja/docs/chapter_sorting/selection_sort.md create mode 100644 ja/docs/chapter_sorting/sorting_algorithm.assets/sorting_examples.png create mode 100644 ja/docs/chapter_sorting/sorting_algorithm.md create mode 100644 ja/docs/chapter_sorting/summary.assets/sorting_algorithms_comparison.png create mode 100644 ja/docs/chapter_sorting/summary.md create mode 100644 ja/docs/chapter_stack_and_queue/deque.assets/array_deque_step1.png create mode 100644 ja/docs/chapter_stack_and_queue/deque.assets/array_deque_step2_push_last.png create mode 100644 ja/docs/chapter_stack_and_queue/deque.assets/array_deque_step3_push_first.png create mode 100644 ja/docs/chapter_stack_and_queue/deque.assets/array_deque_step4_pop_last.png create mode 100644 ja/docs/chapter_stack_and_queue/deque.assets/array_deque_step5_pop_first.png create mode 100644 ja/docs/chapter_stack_and_queue/deque.assets/deque_operations.png create mode 100644 ja/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step1.png create mode 100644 ja/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step2_push_last.png create mode 100644 ja/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step3_push_first.png create mode 100644 ja/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step4_pop_last.png create mode 100644 ja/docs/chapter_stack_and_queue/deque.assets/linkedlist_deque_step5_pop_first.png create mode 100644 ja/docs/chapter_stack_and_queue/deque.md create mode 100644 ja/docs/chapter_stack_and_queue/index.md create mode 100644 ja/docs/chapter_stack_and_queue/queue.assets/array_queue_step1.png create mode 100644 ja/docs/chapter_stack_and_queue/queue.assets/array_queue_step2_push.png create mode 100644 ja/docs/chapter_stack_and_queue/queue.assets/array_queue_step3_pop.png create mode 100644 ja/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step1.png create mode 100644 ja/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step2_push.png create mode 100644 ja/docs/chapter_stack_and_queue/queue.assets/linkedlist_queue_step3_pop.png create mode 100644 ja/docs/chapter_stack_and_queue/queue.assets/queue_operations.png create mode 100644 ja/docs/chapter_stack_and_queue/queue.md create mode 100644 ja/docs/chapter_stack_and_queue/stack.assets/array_stack_step1.png create mode 100644 ja/docs/chapter_stack_and_queue/stack.assets/array_stack_step2_push.png create mode 100644 ja/docs/chapter_stack_and_queue/stack.assets/array_stack_step3_pop.png create mode 100644 ja/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step1.png create mode 100644 ja/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step2_push.png create mode 100644 ja/docs/chapter_stack_and_queue/stack.assets/linkedlist_stack_step3_pop.png create mode 100644 ja/docs/chapter_stack_and_queue/stack.assets/stack_operations.png create mode 100644 ja/docs/chapter_stack_and_queue/stack.md create mode 100644 ja/docs/chapter_stack_and_queue/summary.md create mode 100644 ja/docs/chapter_tree/array_representation_of_tree.assets/array_representation_binary_tree.png create mode 100644 ja/docs/chapter_tree/array_representation_of_tree.assets/array_representation_complete_binary_tree.png create mode 100644 ja/docs/chapter_tree/array_representation_of_tree.assets/array_representation_with_empty.png create mode 100644 ja/docs/chapter_tree/array_representation_of_tree.assets/array_representation_without_empty.png create mode 100644 ja/docs/chapter_tree/array_representation_of_tree.md create mode 100644 ja/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_inserting_node.png create mode 100644 ja/docs/chapter_tree/avl_tree.assets/avltree_degradation_from_removing_node.png create mode 100644 ja/docs/chapter_tree/avl_tree.assets/avltree_left_right_rotate.png create mode 100644 ja/docs/chapter_tree/avl_tree.assets/avltree_left_rotate.png create mode 100644 ja/docs/chapter_tree/avl_tree.assets/avltree_left_rotate_with_grandchild.png create mode 100644 ja/docs/chapter_tree/avl_tree.assets/avltree_right_left_rotate.png create mode 100644 ja/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step1.png create mode 100644 ja/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step2.png create mode 100644 ja/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step3.png create mode 100644 ja/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_step4.png create mode 100644 ja/docs/chapter_tree/avl_tree.assets/avltree_right_rotate_with_grandchild.png create mode 100644 ja/docs/chapter_tree/avl_tree.assets/avltree_rotation_cases.png create mode 100644 ja/docs/chapter_tree/avl_tree.md create mode 100644 ja/docs/chapter_tree/binary_search_tree.assets/binary_search_tree.png create mode 100644 ja/docs/chapter_tree/binary_search_tree.assets/bst_degradation.png create mode 100644 ja/docs/chapter_tree/binary_search_tree.assets/bst_inorder_traversal.png create mode 100644 ja/docs/chapter_tree/binary_search_tree.assets/bst_insert.png create mode 100644 ja/docs/chapter_tree/binary_search_tree.assets/bst_remove_case1.png create mode 100644 ja/docs/chapter_tree/binary_search_tree.assets/bst_remove_case2.png create mode 100644 ja/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step1.png create mode 100644 ja/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step2.png create mode 100644 ja/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step3.png create mode 100644 ja/docs/chapter_tree/binary_search_tree.assets/bst_remove_case3_step4.png create mode 100644 ja/docs/chapter_tree/binary_search_tree.assets/bst_search_step1.png create mode 100644 ja/docs/chapter_tree/binary_search_tree.assets/bst_search_step2.png create mode 100644 ja/docs/chapter_tree/binary_search_tree.assets/bst_search_step3.png create mode 100644 ja/docs/chapter_tree/binary_search_tree.assets/bst_search_step4.png create mode 100644 ja/docs/chapter_tree/binary_search_tree.md create mode 100644 ja/docs/chapter_tree/binary_tree.assets/balanced_binary_tree.png create mode 100644 ja/docs/chapter_tree/binary_tree.assets/binary_tree_add_remove.png create mode 100644 ja/docs/chapter_tree/binary_tree.assets/binary_tree_best_worst_cases.png create mode 100644 ja/docs/chapter_tree/binary_tree.assets/binary_tree_definition.png create mode 100644 ja/docs/chapter_tree/binary_tree.assets/binary_tree_terminology.png create mode 100644 ja/docs/chapter_tree/binary_tree.assets/complete_binary_tree.png create mode 100644 ja/docs/chapter_tree/binary_tree.assets/full_binary_tree.png create mode 100644 ja/docs/chapter_tree/binary_tree.assets/perfect_binary_tree.png create mode 100644 ja/docs/chapter_tree/binary_tree.md create mode 100644 ja/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_bfs.png create mode 100644 ja/docs/chapter_tree/binary_tree_traversal.assets/binary_tree_dfs.png create mode 100644 ja/docs/chapter_tree/binary_tree_traversal.assets/preorder_step1.png create mode 100644 ja/docs/chapter_tree/binary_tree_traversal.assets/preorder_step10.png create mode 100644 ja/docs/chapter_tree/binary_tree_traversal.assets/preorder_step11.png create mode 100644 ja/docs/chapter_tree/binary_tree_traversal.assets/preorder_step2.png create mode 100644 ja/docs/chapter_tree/binary_tree_traversal.assets/preorder_step3.png create mode 100644 ja/docs/chapter_tree/binary_tree_traversal.assets/preorder_step4.png create mode 100644 ja/docs/chapter_tree/binary_tree_traversal.assets/preorder_step5.png create mode 100644 ja/docs/chapter_tree/binary_tree_traversal.assets/preorder_step6.png create mode 100644 ja/docs/chapter_tree/binary_tree_traversal.assets/preorder_step7.png create mode 100644 ja/docs/chapter_tree/binary_tree_traversal.assets/preorder_step8.png create mode 100644 ja/docs/chapter_tree/binary_tree_traversal.assets/preorder_step9.png create mode 100644 ja/docs/chapter_tree/binary_tree_traversal.md create mode 100644 ja/docs/chapter_tree/index.md create mode 100644 ja/docs/chapter_tree/summary.md create mode 100644 ja/docs/index.assets/animation.gif create mode 100644 ja/docs/index.assets/animation_dark.gif create mode 100644 ja/docs/index.assets/btn_chinese_edition.svg create mode 100644 ja/docs/index.assets/btn_chinese_edition_dark.svg create mode 100644 ja/docs/index.assets/btn_download_pdf.svg create mode 100644 ja/docs/index.assets/btn_download_pdf_dark.svg create mode 100644 ja/docs/index.assets/btn_read_online.svg create mode 100644 ja/docs/index.assets/btn_read_online_dark.svg create mode 100644 ja/docs/index.assets/comment.gif create mode 100644 ja/docs/index.assets/hello_algo_header.png create mode 100644 ja/docs/index.assets/hello_algo_mindmap_tp.png create mode 100644 ja/docs/index.assets/running_code.gif create mode 100644 ja/docs/index.assets/running_code_dark.gif create mode 100644 ja/docs/index.html create mode 100644 ja/docs/index.md create mode 100644 ja/mkdocs.yml diff --git a/README.md b/README.md index 09bef485f..52201f8f7 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,8 @@ 繁體中文English + | + 日本語

## 关于本书 diff --git a/docs/assets/avatar/avatar_eltociear.jpg b/docs/assets/avatar/avatar_eltociear.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5af9de966deea5876e2c3cd5afaabfe9a28e0e4e GIT binary patch literal 16126 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

{fn)RI5&!J?vbLLd`T6gcqDxt zV!=RV^@zvBlL3P*^dtwT65v=NtV-zV*7IudVH7 zwztcmq01U|Y`fGD3j_D!hE=O%riOq+%K-V#pMkTu0n9V*W|Z=0&Q<}~MUeJljw*w@ zSP4zeP~AMGX6kw1*p=DB>AMxStvxS>%XUpH=JFiGUfy;<5_O-LVyqdOo?)jkvl+a5 zsyD-0(8gRQ3=g;+UO$#abnPIh-k}?_W=-H6InT}W$gQQaRWk?4+<|WXiY6!|`K^($ QrGTi^W>Yjn+TX?h1ytwN^Z)<= literal 0 HcmV?d00001 diff --git a/en/README.md b/en/README.md index 8cc894855..39c17bdff 100644 --- a/en/README.md +++ b/en/README.md @@ -41,6 +41,8 @@ 繁體中文 | English + | + 日本語

## The book diff --git a/ja/CONTRIBUTING.md b/ja/CONTRIBUTING.md new file mode 100644 index 000000000..ecbac04f4 --- /dev/null +++ b/ja/CONTRIBUTING.md @@ -0,0 +1,134 @@ +# 中国語から日本語への貢献ガイドライン + +「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 new file mode 100644 index 000000000..f9c2aad2c --- /dev/null +++ b/ja/README.md @@ -0,0 +1,91 @@ +

+ + +

+ +

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

+ +

+ + +

+ +

+ + +

+ +

+ + + + + + + + + + + + + +

+ +

+ 简体中文 + | + 繁體中文 + | + English + | + 日本語 +

+ +## この本について + +このオープンソースプロジェクトは、データ構造とアルゴリズムの無料で初心者向けの入門講座を作成することを目的としています。 + +- アニメーションによる図解、わかりやすい内容、なめらかな学習曲線により、初心者がデータ構造とアルゴリズムの「知識マップ」を探索できます。 +- ワンクリックでコードを実行でき、読者のプログラミングスキルを向上させ、アルゴリズムの動作原理とデータ構造の基礎となる実装を理解できます。 +- 教えることで学ぶことを促進し、質問や洞察を自由に共有してください。議論を通じて一緒に成長しましょう。 + +この本が役立つと思われた場合は、スター :star: を付けてサポートしてください。ありがとうございます! + +## 推薦の言葉 + +> 「データ構造とアルゴリズムに関するわかりやすい本で、読者が頭と手を使って学ぶように導きます。アルゴリズム初心者に強くお勧めします!」 +> +> **—— 鄧俊輝教授、清華大学コンピュータサイエンス技術学部** + +> 「データ構造とアルゴリズムを学んでいたときに『Hello Algo』があったなら、10倍簡単だったでしょう!」 +> +> **—— Mu Li、Amazon シニアプリンシパルサイエンティスト** + +## 貢献 + +> [!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言語をサポートしています。 + +貴重なご提案とフィードバックを歓迎します。ご質問がある場合は、Issuesを提出するか、WeChat: `krahets-jyd`でお問い合わせください。 + +この本のすべての貢献者に感謝を捧げたいと思います。彼らの無私の献身により、この本がより良いものになりました。貢献者の皆様: + +

+ + + +

+ +## ライセンス + +このリポジトリ内のテキスト、コード、画像、写真、動画は[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)の下でライセンスされています。 diff --git a/ja/codes/cpp/chapter_array_and_linkedlist/array.cpp b/ja/codes/cpp/chapter_array_and_linkedlist/array.cpp new file mode 100644 index 000000000..1bd279f1b --- /dev/null +++ b/ja/codes/cpp/chapter_array_and_linkedlist/array.cpp @@ -0,0 +1,113 @@ +/** + * File: array.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 要素への乱数アクセス */ +int randomAccess(int *nums, int size) { + // [0, size)の範囲で乱数を選択 + 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を挿入 */ +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`の要素を削除 */ +void remove(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; +} + +/* ドライバーコード */ +int main() { + /* 配列を初期化 */ + int size = 5; + int *arr = new int[size]; + cout << "Array arr = "; + printArray(arr, size); + + int *nums = new int[size]{1, 3, 2, 5, 4}; + cout << "Array nums = "; + printArray(nums, size); + + /* 乱数アクセス */ + int randomNum = randomAccess(nums, size); + cout << "Get a random element from nums = " << randomNum << endl; + + /* 長さの拡張 */ + int enlarge = 3; + nums = extend(nums, size, enlarge); + size += enlarge; + cout << "Extend the array length to 8, resulting in nums = "; + printArray(nums, size); + + /* 要素の挿入 */ + insert(nums, size, 6, 3); + cout << "Insert the number 6 at index 3, resulting in nums = "; + printArray(nums, size); + + /* 要素の削除 */ + remove(nums, size, 2); + cout << "Remove the element at index 2, resulting in nums = "; + printArray(nums, size); + + /* 配列の走査 */ + traverse(nums, size); + + /* 要素の検索 */ + int index = find(nums, size, 3); + cout << "Find element 3 in nums, index = " << 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 new file mode 100644 index 000000000..f7ad5eb8f --- /dev/null +++ b/ja/codes/cpp/chapter_array_and_linkedlist/linked_list.cpp @@ -0,0 +1,89 @@ +/** + * File: linked_list.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 連結リストのノード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 == nullptr) + return; + // n0 -> P -> n1 + ListNode *P = n0->next; + ListNode *n1 = P->next; + n0->next = n1; + // メモリを解放 + delete P; +} + +/* 連結リストの`index`番目のノードにアクセス */ +ListNode *access(ListNode *head, int index) { + for (int i = 0; i < index; i++) { + if (head == nullptr) + return nullptr; + head = head->next; + } + return head; +} + +/* 連結リストで値がtargetの最初のノードを検索 */ +int find(ListNode *head, int target) { + int index = 0; + while (head != nullptr) { + if (head->val == target) + return index; + head = head->next; + index++; + } + return -1; +} + +/* ドライバーコード */ +int main() { + /* 連結リストを初期化 */ + // 各ノードを初期化 + 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; + cout << "The initialized linked list is" << endl; + printLinkedList(n0); + + /* ノードを挿入 */ + insert(n0, new ListNode(0)); + cout << "Linked list after inserting the node is" << endl; + printLinkedList(n0); + + /* ノードを削除 */ + remove(n0); + cout << "Linked list after removing the node is" << endl; + printLinkedList(n0); + + /* ノードにアクセス */ + ListNode *node = access(n0, 3); + cout << "The value of the node at index 3 in the linked list = " << node->val << endl; + + /* ノードを検索 */ + int index = find(n0, 2); + cout << "The index of the node with value 2 in the linked list = " << 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 new file mode 100644 index 000000000..62ac38157 --- /dev/null +++ b/ja/codes/cpp/chapter_array_and_linkedlist/list.cpp @@ -0,0 +1,72 @@ +/** + * File: list.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* ドライバーコード */ +int main() { + /* リストを初期化 */ + vector nums = {1, 3, 2, 5, 4}; + cout << "List nums = "; + printVector(nums); + + /* 要素にアクセス */ + int num = nums[1]; + cout << "Access the element at index 1, obtained num = " << num << endl; + + /* 要素を更新 */ + nums[1] = 0; + cout << "Update the element at index 1 to 0, resulting in nums = "; + printVector(nums); + + /* リストをクリア */ + nums.clear(); + cout << "After clearing the list, nums = "; + printVector(nums); + + /* 末尾に要素を追加 */ + nums.push_back(1); + nums.push_back(3); + nums.push_back(2); + nums.push_back(5); + nums.push_back(4); + cout << "After adding elements, nums = "; + printVector(nums); + + /* 中間に要素を挿入 */ + nums.insert(nums.begin() + 3, 6); + cout << "Insert the number 6 at index 3, resulting in nums = "; + printVector(nums); + + /* 要素を削除 */ + nums.erase(nums.begin() + 3); + cout << "Remove the element at index 3, resulting in 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つのリストを連結 */ + vector nums1 = {6, 8, 7, 10, 9}; + nums.insert(nums.end(), nums1.begin(), nums1.end()); + cout << "Concatenate list nums1 to nums, resulting in nums = "; + printVector(nums); + + /* リストをソート */ + sort(nums.begin(), nums.end()); + cout << "After sorting the list, 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 new file mode 100644 index 000000000..d294f6f63 --- /dev/null +++ b/ja/codes/cpp/chapter_array_and_linkedlist/my_list.cpp @@ -0,0 +1,171 @@ +/** + * File: my_list.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* リストクラス */ +class MyList { + private: + int *arr; // 配列(リスト要素を格納) + int arrCapacity = 10; // リストの容量 + int arrSize = 0; // リストの長さ(現在の要素数) + int extendRatio = 2; // リスト拡張時の倍率 + + public: + /* コンストラクタ */ + 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"); + return arr[index]; + } + + /* 要素を更新 */ + void set(int index, int num) { + if (index < 0 || index >= size()) + throw out_of_range("Index out of bounds"); + arr[index] = num; + } + + /* 末尾に要素を追加 */ + void add(int num) { + // 要素数が容量を超えた場合、拡張メカニズムをトリガー + if (size() == capacity()) + extendCapacity(); + arr[size()] = num; + // 要素数を更新 + arrSize++; + } + + /* 中間に要素を挿入 */ + void insert(int index, int num) { + if (index < 0 || index >= size()) + throw out_of_range("Index out of bounds"); + // 要素数が容量を超えた場合、拡張メカニズムをトリガー + if (size() == capacity()) + extendCapacity(); + // `index`より後のすべての要素を1つ後ろに移動 + for (int j = size() - 1; j >= index; j--) { + arr[j + 1] = arr[j]; + } + arr[index] = num; + // 要素数を更新 + arrSize++; + } + + /* 要素を削除 */ + int remove(int index) { + if (index < 0 || index >= size()) + throw out_of_range("Index out of bounds"); + int num = arr[index]; + // `index`より後のすべての要素を1つ前に移動 + for (int j = index; j < size() - 1; j++) { + arr[j] = arr[j + 1]; + } + // 要素数を更新 + arrSize--; + // 削除された要素を返却 + return num; + } + + /* リストを拡張 */ + void extendCapacity() { + // 元の配列の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 toVector() { + // 有効な長さ範囲内の要素のみを変換 + vector vec(size()); + for (int i = 0; i < size(); i++) { + vec[i] = arr[i]; + } + return vec; + } +}; + +/* ドライバーコード */ +int main() { + /* リストを初期化 */ + MyList *nums = new MyList(); + /* 末尾に要素を追加 */ + nums->add(1); + nums->add(3); + nums->add(2); + nums->add(5); + nums->add(4); + cout << "List nums = "; + vector vec = nums->toVector(); + printVector(vec); + cout << "Capacity = " << nums->capacity() << ", length = " << nums->size() << endl; + + /* 中間に要素を挿入 */ + nums->insert(3, 6); + cout << "Insert the number 6 at index 3, resulting in nums = "; + vec = nums->toVector(); + printVector(vec); + + /* 要素を削除 */ + nums->remove(3); + cout << "Remove the element at index 3, resulting in nums = "; + vec = nums->toVector(); + printVector(vec); + + /* 要素にアクセス */ + int num = nums->get(1); + cout << "Access the element at index 1, obtained num = " << num << endl; + + /* 要素を更新 */ + nums->set(1, 0); + cout << "Update the element at index 1 to 0, resulting in nums = "; + vec = nums->toVector(); + printVector(vec); + + /* 拡張メカニズムをテスト */ + for (int i = 0; i < 10; i++) { + // i = 5の時、リストの長さがリストの容量を超え、この時点で拡張メカニズムがトリガーされる + nums->add(i); + } + cout << "After extending, list nums = "; + vec = nums->toVector(); + printVector(vec); + cout << "Capacity = " << nums->capacity() << ", length = " << nums->size() << endl; + + // メモリを解放 + delete nums; + + return 0; +} \ No newline at end of file diff --git a/ja/codes/cpp/chapter_backtracking/n_queens.cpp b/ja/codes/cpp/chapter_backtracking/n_queens.cpp new file mode 100644 index 000000000..de89dc413 --- /dev/null +++ b/ja/codes/cpp/chapter_backtracking/n_queens.cpp @@ -0,0 +1,65 @@ +/** + * File: n_queens.cpp + * Created Time: 2023-05-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* バックトラッキングアルゴリズム: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 クイーンを解く */ +vector>> nQueens(int n) { + // 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>> res; + + backtrack(0, n, state, res, cols, diags1, diags2); + + return res; +} + +/* ドライバーコード */ +int main() { + int n = 4; + vector>> res = nQueens(n); + + cout << "チェスボードの次元を " << n << " として入力" << endl; + cout << "クイーン配置解の総数 = " << res.size() << endl; + for (const vector> &state : res) { + cout << "--------------------" << endl; + for (const vector &row : state) { + printVector(row); + } + } + + 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 new file mode 100644 index 000000000..2d637e71f --- /dev/null +++ b/ja/codes/cpp/chapter_backtracking/permutations_i.cpp @@ -0,0 +1,54 @@ +/** + * File: permutations_i.cpp + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* バックトラッキングアルゴリズム:順列 I */ +void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { + // 状態の長さが要素数と等しくなったら、解を記録 + if (state.size() == choices.size()) { + res.push_back(state); + return; + } + // すべての選択肢を走査 + 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 */ +vector> permutationsI(vector nums) { + vector state; + vector selected(nums.size(), false); + vector> res; + backtrack(state, nums, selected, res); + return res; +} + +/* ドライバーコード */ +int main() { + vector nums = {1, 2, 3}; + + vector> res = permutationsI(nums); + + cout << "入力配列 nums = "; + printVector(nums); + cout << "すべての順列 res = "; + 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 new file mode 100644 index 000000000..0f795da1a --- /dev/null +++ b/ja/codes/cpp/chapter_backtracking/permutations_ii.cpp @@ -0,0 +1,56 @@ +/** + * File: permutations_ii.cpp + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* バックトラッキングアルゴリズム:順列 II */ +void backtrack(vector &state, const vector &choices, vector &selected, vector> &res) { + // 状態の長さが要素数と等しくなったら、解を記録 + if (state.size() == choices.size()) { + res.push_back(state); + return; + } + // すべての選択肢を走査 + 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); // 選択された要素値を記録 + selected[i] = true; + state.push_back(choice); + // 次のラウンドの選択に進む + backtrack(state, choices, selected, res); + // 回退:選択を取り消し、前の状態に復元 + selected[i] = false; + state.pop_back(); + } + } +} + +/* 順列 II */ +vector> permutationsII(vector nums) { + vector state; + vector selected(nums.size(), false); + vector> res; + backtrack(state, nums, selected, res); + return res; +} + +/* ドライバーコード */ +int main() { + vector nums = {1, 1, 2}; + + vector> res = permutationsII(nums); + + cout << "入力配列 nums = "; + printVector(nums); + cout << "すべての順列 res = "; + 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 new file mode 100644 index 000000000..841d2b7e6 --- /dev/null +++ b/ja/codes/cpp/chapter_backtracking/preorder_traversal_i_compact.cpp @@ -0,0 +1,43 @@ +/** + * File: preorder_traversal_i_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector res; + +/* 前順走査:例1 */ +void preOrder(TreeNode *root) { + if (root == nullptr) { + return; + } + if (root->val == 7) { + // 解を記録 + res.push_back(root); + } + preOrder(root->left); + preOrder(root->right); +} + +/* ドライバーコード */ +int main() { + vector arr = {1, 7, 3, 4, 5, 6, 7}; + TreeNode *root = vecToTree(arr); + cout << "\n二分木を初期化" << endl; + printTree(root); + + // 前順走査 + res.clear(); + preOrder(root); + + 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 new file mode 100644 index 000000000..7a34f6d94 --- /dev/null +++ b/ja/codes/cpp/chapter_backtracking/preorder_traversal_ii_compact.cpp @@ -0,0 +1,51 @@ +/** + * File: preorder_traversal_ii_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector path; +vector> res; + +/* 前順走査:例2 */ +void preOrder(TreeNode *root) { + if (root == nullptr) { + return; + } + // 試行 + path.push_back(root); + if (root->val == 7) { + // 解を記録 + res.push_back(path); + } + preOrder(root->left); + preOrder(root->right); + // 回退 + path.pop_back(); +} + +/* ドライバーコード */ +int main() { + vector arr = {1, 7, 3, 4, 5, 6, 7}; + TreeNode *root = vecToTree(arr); + cout << "\n二分木を初期化" << endl; + printTree(root); + + // 前順走査 + path.clear(); + res.clear(); + preOrder(root); + + cout << "\nルートからノード7までのすべてのパスを出力" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + 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_iii_compact.cpp b/ja/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp new file mode 100644 index 000000000..0de63144b --- /dev/null +++ b/ja/codes/cpp/chapter_backtracking/preorder_traversal_iii_compact.cpp @@ -0,0 +1,52 @@ +/** + * File: preorder_traversal_iii_compact.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +vector path; +vector> res; + +/* 前順走査:例3 */ +void preOrder(TreeNode *root) { + // 剪定 + if (root == nullptr || root->val == 3) { + return; + } + // 試行 + path.push_back(root); + if (root->val == 7) { + // 解を記録 + res.push_back(path); + } + preOrder(root->left); + preOrder(root->right); + // 回退 + path.pop_back(); +} + +/* ドライバーコード */ +int main() { + vector arr = {1, 7, 3, 4, 5, 6, 7}; + TreeNode *root = vecToTree(arr); + cout << "\n二分木を初期化" << endl; + printTree(root); + + // 前順走査 + path.clear(); + res.clear(); + preOrder(root); + + cout << "\nルートからノード7までのすべてのパスを出力、値3のノードは含まない" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + 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_iii_template.cpp b/ja/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp new file mode 100644 index 000000000..8c6f976ee --- /dev/null +++ b/ja/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp @@ -0,0 +1,79 @@ +/** + * File: preorder_traversal_iii_template.cpp + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 現在の状態が解かどうかを判定 */ +bool isSolution(vector &state) { + return !state.empty() && state.back()->val == 7; +} + +/* 解を記録 */ +void recordSolution(vector &state, vector> &res) { + res.push_back(state); +} + +/* 現在の状態下で選択が合法かどうかを判定 */ +bool isValid(vector &state, TreeNode *choice) { + return choice != nullptr && choice->val != 3; +} + +/* 状態を更新 */ +void makeChoice(vector &state, TreeNode *choice) { + state.push_back(choice); +} + +/* 状態を復元 */ +void undoChoice(vector &state, TreeNode *choice) { + state.pop_back(); +} + +/* バックトラッキングアルゴリズム:例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); + } + } +} + +/* ドライバーコード */ +int main() { + vector arr = {1, 7, 3, 4, 5, 6, 7}; + TreeNode *root = vecToTree(arr); + cout << "\n二分木を初期化" << endl; + printTree(root); + + // バックトラッキングアルゴリズム + vector state; + vector choices = {root}; + vector> res; + backtrack(state, choices, res); + + cout << "\nルートからノード7までのすべてのパスを出力、パスには値3のノードを含まないことが要求される" << endl; + for (vector &path : res) { + vector vals; + for (TreeNode *node : path) { + vals.push_back(node->val); + } + 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 new file mode 100644 index 000000000..72c49f4df --- /dev/null +++ b/ja/codes/cpp/chapter_backtracking/subset_sum_i.cpp @@ -0,0 +1,57 @@ +/** + * File: subset_sum_i.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* バックトラッキングアルゴリズム:部分集合和 I */ +void backtrack(vector &state, int target, vector &choices, int start, vector> &res) { + // 部分集合の和がtargetと等しいとき、解を記録 + if (target == 0) { + res.push_back(state); + return; + } + // すべての選択肢を走査 + // 剪定二:startから走査を開始し、重複する部分集合の生成を回避 + for (int i = start; i < choices.size(); i++) { + // 剪定一:部分集合の和がtargetを超えた場合、即座にループを終了 + // 配列がソートされているため、後の要素はさらに大きく、部分集合の和は必ずtargetを超える + if (target - choices[i] < 0) { + break; + } + // 試行:選択を行い、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) { + vector state; // 状態(部分集合) + sort(nums.begin(), nums.end()); // nums をソート + int start = 0; // 走査の開始点 + vector> res; // 結果リスト(部分集合リスト) + backtrack(state, target, nums, start, res); + return res; +} + +/* ドライバーコード */ +int main() { + vector nums = {3, 4, 5}; + int target = 9; + + vector> res = subsetSumI(nums, target); + + cout << "入力配列 nums = "; + printVector(nums); + cout << "target = " << target << 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 new file mode 100644 index 000000000..8fcf2b7cc --- /dev/null +++ b/ja/codes/cpp/chapter_backtracking/subset_sum_i_naive.cpp @@ -0,0 +1,55 @@ +/** + * File: subset_sum_i_naive.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* バックトラッキングアルゴリズム:部分集合和 I */ +void backtrack(vector &state, int target, int total, vector &choices, vector> &res) { + // 部分集合の和がtargetと等しいとき、解を記録 + if (total == target) { + res.push_back(state); + return; + } + // すべての選択肢を走査 + for (int i = 0; i < choices.size(); i++) { + // 剪定:部分集合の和がtargetを超えた場合、その選択をスキップ + if (total + choices[i] > target) { + continue; + } + // 試行:選択を行い、要素とtotalを更新 + state.push_back(choices[i]); + // 次のラウンドの選択に進む + backtrack(state, target, total + choices[i], choices, res); + // 回退:選択を取り消し、前の状態に復元 + state.pop_back(); + } +} + +/* 部分集合和 I を解く(重複する部分集合を含む) */ +vector> subsetSumINaive(vector nums, int target) { + vector state; // 状態(部分集合) + int total = 0; // 部分集合の和 + vector> res; // 結果リスト(部分集合リスト) + backtrack(state, target, total, nums, res); + return res; +} + +/* ドライバーコード */ +int main() { + vector nums = {3, 4, 5}; + int target = 9; + + vector> res = subsetSumINaive(nums, target); + + cout << "入力配列 nums = "; + printVector(nums); + cout << "target = " << target << 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 new file mode 100644 index 000000000..4649d51b2 --- /dev/null +++ b/ja/codes/cpp/chapter_backtracking/subset_sum_ii.cpp @@ -0,0 +1,62 @@ +/** + * File: subset_sum_ii.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* バックトラッキングアルゴリズム:部分集合和 II */ +void backtrack(vector &state, int target, vector &choices, int start, vector> &res) { + // 部分集合の和がtargetと等しいとき、解を記録 + if (target == 0) { + res.push_back(state); + return; + } + // すべての選択肢を走査 + // 剪定二:startから走査を開始し、重複する部分集合の生成を回避 + // 剪定三:startから走査を開始し、同じ要素の繰り返し選択を回避 + for (int i = start; i < choices.size(); i++) { + // 剪定一:部分集合の和がtargetを超えた場合、即座にループを終了 + // 配列がソートされているため、後の要素はさらに大きく、部分集合の和は必ずtargetを超える + if (target - choices[i] < 0) { + break; + } + // 剪定四:要素が左の要素と等しい場合、検索ブランチの重複を示すのでスキップ + if (i > start && choices[i] == choices[i - 1]) { + continue; + } + // 試行:選択を行い、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) { + vector state; // 状態(部分集合) + sort(nums.begin(), nums.end()); // nums をソート + int start = 0; // 走査の開始点 + vector> res; // 結果リスト(部分集合リスト) + backtrack(state, target, nums, start, res); + return res; +} + +/* ドライバーコード */ +int main() { + vector nums = {4, 4, 5}; + int target = 9; + + vector> res = subsetSumII(nums, target); + + cout << "入力配列 nums = "; + printVector(nums); + cout << "target = " << target << endl; + cout << "和が " << target << " のすべての部分集合 res = " << endl; + printVectorMatrix(res); + + return 0; +} \ 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 new file mode 100644 index 000000000..4ad0f399b --- /dev/null +++ b/ja/codes/cpp/chapter_computational_complexity/iteration.cpp @@ -0,0 +1,76 @@ +/** + * File: iteration.cpp + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 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; +} + +/* 2重 for ループ */ +string nestedForLoop(int n) { + ostringstream 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.str(); +} + +/* ドライバーコード */ +int main() { + int n = 5; + int res; + + res = forLoop(n); + cout << "\nfor ループの合計結果 res = " << res << endl; + + res = whileLoop(n); + cout << "\nwhile ループの合計結果 res = " << res << endl; + + res = whileLoopII(n); + cout << "\nwhile ループ(2つの更新)の合計結果 res = " << res << endl; + + string resStr = nestedForLoop(n); + cout << "\n2重 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 new file mode 100644 index 000000000..a481f89e8 --- /dev/null +++ b/ja/codes/cpp/chapter_computational_complexity/recursion.cpp @@ -0,0 +1,78 @@ +/** + * File: recursion.cpp + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 再帰 */ +int recur(int n) { + // 終了条件 + if (n == 1) + 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(); + } + // 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; +} + +/* ドライバーコード */ +int main() { + int n = 5; + int res; + + res = recur(n); + cout << "\n再帰関数の合計結果 res = " << res << endl; + + res = forLoopRecur(n); + cout << "\n反復を使用して再帰をシミュレートした合計結果 res = " << res << endl; + + res = tailRecur(n, 0); + cout << "\n末尾再帰関数の合計結果 res = " << res << endl; + + res = fib(n); + cout << "フィボナッチ数列の第 " << 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 new file mode 100644 index 000000000..2d61b66c8 --- /dev/null +++ b/ja/codes/cpp/chapter_computational_complexity/space_complexity.cpp @@ -0,0 +1,107 @@ +/** + * File: space_complexity.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 関数 */ +int func() { + // 何らかの操作を実行 + return 0; +} + +/* 定数計算量 */ +void constant(int n) { + // 定数、変数、オブジェクトは O(1) 空間を占める + const int a = 0; + int b = 0; + vector nums(10000); + ListNode node(0); + // ループ内の変数は O(1) 空間を占める + for (int i = 0; i < n; i++) { + int c = 0; + } + // ループ内の関数は O(1) 空間を占める + for (int i = 0; i < n; i++) { + func(); + } +} + +/* 線形計算量 */ +void linear(int n) { + // 長さ n の配列は O(n) 空間を占める + vector nums(n); + // 長さ n のリストは O(n) 空間を占める + vector nodes; + for (int i = 0; i < n; i++) { + nodes.push_back(ListNode(i)); + } + // 長さ 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) + return; + linearRecur(n - 1); +} + +/* 二次計算量 */ +void quadratic(int n) { + // 二次元リストは O(n^2) 空間を占める + vector> numMatrix; + for (int i = 0; i < n; i++) { + vector tmp; + for (int j = 0; j < n; j++) { + tmp.push_back(0); + } + numMatrix.push_back(tmp); + } +} + +/* 二次計算量(再帰実装) */ +int quadraticRecur(int n) { + if (n <= 0) + return 0; + vector nums(n); + cout << "再帰 n = " << n << ", nums の長さ = " << nums.size() << endl; + return quadraticRecur(n - 1); +} + +/* 指数計算量(完全二分木の構築) */ +TreeNode *buildTree(int n) { + if (n == 0) + return nullptr; + TreeNode *root = new TreeNode(0); + root->left = buildTree(n - 1); + root->right = buildTree(n - 1); + return root; +} + +/* ドライバーコード */ +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 new file mode 100644 index 000000000..c20257b8e --- /dev/null +++ b/ja/codes/cpp/chapter_computational_complexity/time_complexity.cpp @@ -0,0 +1,168 @@ +/** + * File: time_complexity.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 定数計算量 */ +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(vector &nums) { + int count = 0; + // ループ回数は配列の長さに比例 + for (int num : 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(vector &nums) { + int count = 0; // カウンター + // 外側ループ:未ソート範囲は [0, i] + for (int i = nums.size() - 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, base = 1; + // セルは毎ラウンド2つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成 + for (int i = 0; i < n; i++) { + for (int 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 (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; +} + +/* ドライバーコード */ +int main() { + // n を変更して、さまざまな計算量での操作回数の変化傾向を体験可能 + int n = 8; + cout << "入力データサイズ n = " << n << endl; + + int count = constant(n); + cout << "定数計算量の操作回数 = " << count << endl; + + count = linear(n); + cout << "線形計算量の操作回数 = " << count << endl; + vector arr(n); + count = arrayTraversal(arr); + cout << "線形計算量の操作回数(配列走査) = " << count << endl; + + count = quadratic(n); + 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; + + count = exponential(n); + cout << "指数計算量の操作回数(ループ実装) = " << count << endl; + count = expRecur(n); + cout << "指数計算量の操作回数(再帰実装) = " << count << endl; + + count = logarithmic(n); + cout << "対数計算量の操作回数(ループ実装) = " << count << endl; + count = logRecur(n); + cout << "対数計算量の操作回数(再帰実装) = " << count << endl; + + count = linearLogRecur(n); + cout << "線形対数計算量の操作回数(再帰実装) = " << count << endl; + + count = factorialRecur(n); + 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 new file mode 100644 index 000000000..517a610be --- /dev/null +++ b/ja/codes/cpp/chapter_computational_complexity/worst_best_time_complexity.cpp @@ -0,0 +1,45 @@ +/** + * File: worst_best_time_complexity.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 要素 {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のインデックスを見つける */ +int findOne(vector &nums) { + for (int i = 0; i < nums.size(); i++) { + // 要素1が配列の先頭にある場合、最良時間計算量 O(1) を達成 + // 要素1が配列の末尾にある場合、最悪時間計算量 O(n) を達成 + if (nums[i] == 1) + return i; + } + return -1; +} + +/* ドライバーコード */ +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 ] をシャッフル後 = "; + printVector(nums); + cout << "数値1のインデックスは " << index << endl; + } + return 0; +} \ 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 new file mode 100644 index 000000000..ca1c0608c --- /dev/null +++ b/ja/codes/cpp/chapter_divide_and_conquer/binary_search_recur.cpp @@ -0,0 +1,46 @@ +/** + * File: binary_search_recur.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二分探索:問題 f(i, j) */ +int dfs(vector &nums, int target, int i, int j) { + // 区間が空の場合、対象要素が存在しないことを示すため、-1 を返す + if (i > j) { + return -1; + } + // 中点インデックス m を計算 + int m = i + (j - i) / 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(vector &nums, int target) { + int n = nums.size(); + // 問題 f(0, n-1) を解く + return dfs(nums, target, 0, n - 1); +} + +/* ドライバーコード */ +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; + + 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 new file mode 100644 index 000000000..23d3ce215 --- /dev/null +++ b/ja/codes/cpp/chapter_divide_and_conquer/build_tree.cpp @@ -0,0 +1,51 @@ +/** + * File: build_tree.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#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 を問い合わせて左右の部分木を分割 + 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) { + // ハッシュテーブルを初期化し、中間順序の要素からインデックスへのマッピングを格納 + unordered_map inorderMap; + for (int i = 0; i < inorder.size(); i++) { + inorderMap[inorder[i]] = i; + } + TreeNode *root = dfs(preorder, inorderMap, 0, 0, inorder.size() - 1); + return root; +} + +/* ドライバーコード */ +int main() { + vector preorder = {3, 9, 2, 1, 7}; + vector inorder = {9, 3, 1, 2, 7}; + cout << "前順走査 = "; + printVector(preorder); + cout << "中間順序走査 = "; + printVector(inorder); + + TreeNode *root = buildTree(preorder, inorder); + 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 new file mode 100644 index 000000000..918d7be3c --- /dev/null +++ b/ja/codes/cpp/chapter_divide_and_conquer/hanota.cpp @@ -0,0 +1,66 @@ +/** + * File: hanota.cpp + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 円盤を移動 */ +void move(vector &src, vector &tar) { + // src の最上部から円盤を取り出す + int pan = src.back(); + src.pop_back(); + // 円盤を tar の最上部に配置 + tar.push_back(pan); +} + +/* ハノイの塔問題 f(i) を解く */ +void dfs(int i, vector &src, vector &buf, vector &tar) { + // src に円盤が1つだけ残っている場合、それを tar に移動 + if (i == 1) { + move(src, tar); + return; + } + // 部分問題 f(i-1):tar の助けを借りて、上位 i-1 個の円盤を src から buf に移動 + dfs(i - 1, src, tar, buf); + // 部分問題 f(1):残りの1つの円盤を src から tar に移動 + move(src, tar); + // 部分問題 f(i-1):src の助けを借りて、上位 i-1 個の円盤を buf から tar に移動 + dfs(i - 1, buf, src, tar); +} + +/* ハノイの塔問題を解く */ +void solveHanota(vector &A, vector &B, vector &C) { + int n = A.size(); + // B の助けを借りて、上位 n 個の円盤を A から C に移動 + dfs(n, A, B, C); +} + +/* ドライバーコード */ +int main() { + // リストの末尾が柱の最上部 + vector A = {5, 4, 3, 2, 1}; + vector B = {}; + vector C = {}; + + cout << "初期状態:\n"; + cout << "A ="; + printVector(A); + cout << "B ="; + printVector(B); + cout << "C ="; + printVector(C); + + solveHanota(A, B, C); + + cout << "円盤移動後:\n"; + cout << "A ="; + printVector(A); + cout << "B ="; + printVector(B); + cout << "C ="; + printVector(C); + + return 0; +} \ 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 new file mode 100644 index 000000000..029aff547 --- /dev/null +++ b/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_backtrack.cpp @@ -0,0 +1,42 @@ +/** + * File: climbing_stairs_backtrack.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* バックトラッキング */ +void backtrack(vector &choices, int state, int n, vector &res) { + // n段目に到達したとき、解の数に1を加える + if (state == n) + res[0]++; + // すべての選択肢を走査 + for (auto &choice : choices) { + // 剪定: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] を使用して解の数を記録 + backtrack(choices, state, n, res); + return res[0]; +} + +/* ドライバーコード */ +int main() { + int n = 9; + + int res = climbingStairsBacktrack(n); + 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 new file mode 100644 index 000000000..c8d317aec --- /dev/null +++ b/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_constraint_dp.cpp @@ -0,0 +1,37 @@ +/** + * File: climbing_stairs_constraint_dp.cpp + * Created Time: 2023-07-01 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 制約付き階段登り:動的プログラミング */ +int climbingStairsConstraintDP(int n) { + if (n == 1 || n == 2) { + return 1; + } + // 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]; + } + return dp[n][1] + dp[n][2]; +} + +/* ドライバーコード */ +int main() { + int n = 9; + + int res = climbingStairsConstraintDP(n); + 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 new file mode 100644 index 000000000..37cdbbd56 --- /dev/null +++ b/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs.cpp @@ -0,0 +1,32 @@ +/** + * File: climbing_stairs_dfs.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 探索 */ +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); +} + +/* ドライバーコード */ +int main() { + int n = 9; + + int res = climbingStairsDFS(n); + 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 new file mode 100644 index 000000000..df8191eac --- /dev/null +++ b/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dfs_mem.cpp @@ -0,0 +1,39 @@ +/** + * File: climbing_stairs_dfs_mem.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* メモ化探索 */ +int dfs(int i, vector &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 は記録なしを意味する + vector mem(n + 1, -1); + return dfs(n, mem); +} + +/* ドライバーコード */ +int main() { + int n = 9; + + int res = climbingStairsDFSMem(n); + 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 new file mode 100644 index 000000000..5e96f7416 --- /dev/null +++ b/ja/codes/cpp/chapter_dynamic_programming/climbing_stairs_dp.cpp @@ -0,0 +1,49 @@ +/** + * File: climbing_stairs_dp.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 階段登り:動的プログラミング */ +int climbingStairsDP(int n) { + if (n == 1 || n == 2) + return n; + // 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; + int a = 1, b = 2; + for (int i = 3; i <= n; i++) { + int tmp = b; + b = a + b; + a = tmp; + } + return b; +} + +/* ドライバーコード */ +int main() { + int n = 9; + + int res = climbingStairsDP(n); + cout << n << "段の階段を登る解は" << res << "通りです" << endl; + + res = climbingStairsDPComp(n); + 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 new file mode 100644 index 000000000..e67f9471f --- /dev/null +++ b/ja/codes/cpp/chapter_dynamic_programming/coin_change.cpp @@ -0,0 +1,70 @@ +/** + * File: coin_change.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 硬貨両替:動的プログラミング */ +int coinChangeDP(vector &coins, int amt) { + int n = coins.size(); + int MAX = amt + 1; + // 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 を選択しない + 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(vector &coins, int amt) { + int n = coins.size(); + int MAX = amt + 1; + // 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 を選択しない + dp[a] = dp[a]; + } else { + // 選択しない場合と硬貨 i を選択する場合のより小さい値 + dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1); + } + } + } + return dp[amt] != MAX ? dp[amt] : -1; +} + +/* ドライバーコード */ +int main() { + vector coins = {1, 2, 5}; + int amt = 4; + + // 動的プログラミング + int res = coinChangeDP(coins, amt); + cout << "目標金額を作るのに必要な最小硬貨数は " << res << " です" << endl; + + // 空間最適化動的プログラミング + res = coinChangeDPComp(coins, amt); + 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 new file mode 100644 index 000000000..4b3bb93dc --- /dev/null +++ b/ja/codes/cpp/chapter_dynamic_programming/coin_change_ii.cpp @@ -0,0 +1,68 @@ +/** + * File: coin_change_ii.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 硬貨両替 II:動的プログラミング */ +int coinChangeIIDP(vector &coins, int amt) { + int n = coins.size(); + // DPテーブルを初期化 + vector> dp(n + 1, vector(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 を選択する場合の2つの選択肢の合計 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; +} + +/* 硬貨両替 II:空間最適化動的プログラミング */ +int coinChangeIIDPComp(vector &coins, int amt) { + int n = coins.size(); + // 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 を選択しない + dp[a] = dp[a]; + } else { + // 選択しない場合と硬貨 i を選択する場合の2つの選択肢の合計 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; +} + +/* ドライバーコード */ +int main() { + vector coins = {1, 2, 5}; + int amt = 5; + + // 動的プログラミング + int res = coinChangeIIDP(coins, amt); + cout << "目標金額を作る硬貨の組み合わせ数は " << res << " です" << endl; + + // 空間最適化動的プログラミング + res = coinChangeIIDPComp(coins, amt); + 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 new file mode 100644 index 000000000..3abce88b2 --- /dev/null +++ b/ja/codes/cpp/chapter_dynamic_programming/edit_distance.cpp @@ -0,0 +1,72 @@ +/** + * File: edit_distance.cpp + * Created Time: 2023-07-13 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 編集距離:ブルートフォース探索 */ +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 del = editDistanceDFS(s, t, i - 1, j); + int replace = editDistanceDFS(s, t, i - 1, j - 1); + // 最小編集数を返す + return min(min(insert, del), replace) + 1; +} + +/* 編集距離:動的プログラミング */ +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つの文字をスキップ + 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 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; + + // 動的プログラミング + res = editDistanceDP(s, t); + cout << s << " を " << t << " に変更するには最低 " << res << " 回の編集が必要です" << endl; + + 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 new file mode 100644 index 000000000..a928d55ac --- /dev/null +++ b/ja/codes/cpp/chapter_dynamic_programming/knapsack.cpp @@ -0,0 +1,84 @@ +/** + * File: knapsack.cpp + * Created Time: 2023-07-10 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 0-1 ナップサック:ブルートフォース探索 */ +int knapsackDFS(vector &wgt, vector &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 knapsackDP(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // 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 を選択しない + 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(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // 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 を選択する場合のより大きい値 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* ドライバーコード */ +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; + + // 動的プログラミング + res = knapsackDP(wgt, val, cap); + cout << "ナップサック容量内での最大値は " << res << " です" << endl; + + // 空間最適化動的プログラミング + res = knapsackDPComp(wgt, val, cap); + 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 new file mode 100644 index 000000000..eb0b6713e --- /dev/null +++ b/ja/codes/cpp/chapter_dynamic_programming/min_cost_climbing_stairs_dp.cpp @@ -0,0 +1,57 @@ +/** + * File: min_cost_climbing_stairs_dp.cpp + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 最小コスト階段登り:動的プログラミング */ +int minCostClimbingStairsDP(vector &cost) { + int n = cost.size() - 1; + if (n == 1 || n == 2) + return cost[n]; + // 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) + 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; +} + +/* ドライバーコード */ +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; + + int res = minCostClimbingStairsDP(cost); + cout << "階段を登るための最小コスト " << res << endl; + + res = minCostClimbingStairsDPComp(cost); + 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 new file mode 100644 index 000000000..0f92ba1f6 --- /dev/null +++ b/ja/codes/cpp/chapter_dynamic_programming/min_path_sum.cpp @@ -0,0 +1,68 @@ +/** + * File: min_path_sum.cpp + * Created Time: 2023-07-10 + * Author: krahets (krahets@163.com) + */ + +#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) への最小パスコストを計算 + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // 左上から (i, j) への最小パスコストを返す + return min(left, up) + grid[i][j]; +} + +/* 最小パス和:動的プログラミング */ +int minPathSumDP(vector> &grid) { + int n = grid.size(), m = grid[0].size(); + // 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]; + } + } + return dp[n - 1][m - 1]; +} + +/* ドライバーコード */ +int main() { + 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; + + // 動的プログラミング + res = minPathSumDP(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 new file mode 100644 index 000000000..605562833 --- /dev/null +++ b/ja/codes/cpp/chapter_dynamic_programming/unbounded_knapsack.cpp @@ -0,0 +1,64 @@ +/** + * File: unbounded_knapsack.cpp + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 完全ナップサック:動的プログラミング */ +int unboundedKnapsackDP(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // 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 を選択しない + 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(vector &wgt, vector &val, int cap) { + int n = wgt.size(); + // 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 を選択しない + dp[c] = dp[c]; + } else { + // 選択しない場合とアイテム i を選択する場合のより大きい値 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[cap]; +} + +/* ドライバーコード */ +int main() { + vector wgt = {1, 2, 3}; + vector val = {5, 11, 15}; + int cap = 4; + + // 動的プログラミング + int res = unboundedKnapsackDP(wgt, val, cap); + cout << "ナップサック容量内での最大値は " << res << " です" << endl; + + // 空間最適化動的プログラミング + res = unboundedKnapsackDPComp(wgt, val, cap); + cout << "ナップサック容量内での最大値は " << res << " です" << endl; + + return 0; +} \ No newline at end of file diff --git a/ja/codes/cpp/chapter_graph/graph_adjacency_list.cpp b/ja/codes/cpp/chapter_graph/graph_adjacency_list.cpp new file mode 100644 index 000000000..b661ac421 --- /dev/null +++ b/ja/codes/cpp/chapter_graph/graph_adjacency_list.cpp @@ -0,0 +1,90 @@ +/** + * File: graph_adjacency_list.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp), krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 隣接リストに基づく無向グラフクラス */ +class GraphAdjList { + public: + // 隣接リスト、キー:頂点、値:その頂点のすべての隣接頂点 + unordered_map> adjList; + + /* ベクターから指定されたノードを削除 */ + void remove(vector &vec, Vertex *vet) { + for (int i = 0; i < vec.size(); i++) { + if (vec[i] == vet) { + vec.erase(vec.begin() + i); + break; + } + } + } + + /* コンストラクタ */ + GraphAdjList(const vector> &edges) { + // すべての頂点と辺を追加 + for (const vector &edge : edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* 頂点数を取得 */ + int size() { + return adjList.size(); + } + + /* 辺を追加 */ + void addEdge(Vertex *vet1, Vertex *vet2) { + if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) + throw invalid_argument("Vertex does not exist"); + // 辺 vet1 - vet2 を追加 + adjList[vet1].push_back(vet2); + adjList[vet2].push_back(vet1); + } + + /* 辺を削除 */ + void removeEdge(Vertex *vet1, Vertex *vet2) { + if (!adjList.count(vet1) || !adjList.count(vet2) || vet1 == vet2) + throw invalid_argument("Vertex does not exist"); + // 辺 vet1 - vet2 を削除 + remove(adjList[vet1], vet2); + remove(adjList[vet2], vet1); + } + + /* 頂点を追加 */ + 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に対応する連結リストを削除 + adjList.erase(vet); + // 他の頂点の連結リストを走査し、vetを含むすべての辺を削除 + for (auto &adj : adjList) { + remove(adj.second, vet); + } + } + + /* 隣接リストを印刷 */ + void print() { + cout << "隣接リスト =" << endl; + for (auto &adj : adjList) { + const auto &key = adj.first; + const auto &vec = adj.second; + cout << key->val << ": "; + printVector(vetsToVals(vec)); + } + } +}; + +// テストケースはgraph_adjacency_list_test.cppを参照 \ No newline at end of file diff --git a/ja/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp b/ja/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp new file mode 100644 index 000000000..137bb4199 --- /dev/null +++ b/ja/codes/cpp/chapter_graph/graph_adjacency_matrix.cpp @@ -0,0 +1,127 @@ +/** + * File: graph_adjacency_matrix.cpp + * Created Time: 2023-02-09 + * Author: what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* 隣接行列に基づく無向グラフクラス */ +class GraphAdjMat { + vector vertices; // 頂点リスト、要素は「頂点値」を表し、インデックスは「頂点インデックス」を表す + vector> adjMat; // 隣接行列、行と列のインデックスは「頂点インデックス」に対応 + + public: + /* コンストラクタ */ + GraphAdjMat(const vector &vertices, const vector> &edges) { + // 頂点を追加 + for (int val : vertices) { + addVertex(val); + } + // 辺を追加 + // 辺の要素は頂点インデックスを表す + for (const vector &edge : edges) { + addEdge(edge[0], edge[1]); + } + } + + /* 頂点数を取得 */ + int size() const { + return vertices.size(); + } + + /* 頂点を追加 */ + void addVertex(int val) { + int n = size(); + // 頂点リストに新しい頂点値を追加 + vertices.push_back(val); + // 隣接行列に行を追加 + adjMat.emplace_back(vector(n, 0)); + // 隣接行列に列を追加 + for (vector &row : adjMat) { + row.push_back(0); + } + } + + /* 頂点を削除 */ + void removeVertex(int index) { + if (index >= size()) { + throw out_of_range("Vertex does not exist"); + } + // 頂点リストから`index`の頂点を削除 + vertices.erase(vertices.begin() + index); + // 隣接行列から`index`の行を削除 + adjMat.erase(adjMat.begin() + index); + // 隣接行列から`index`の列を削除 + for (vector &row : adjMat) { + row.erase(row.begin() + index); + } + } + + /* 辺を追加 */ + // パラメータi、jは頂点要素のインデックスに対応 + 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"); + } + // 無向グラフでは、隣接行列は主対角線について対称、即ち(i, j) == (j, i)を満たす + adjMat[i][j] = 1; + adjMat[j][i] = 1; + } + + /* 辺を削除 */ + // パラメータi、jは頂点要素のインデックスに対応 + 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"); + } + adjMat[i][j] = 0; + adjMat[j][i] = 0; + } + + /* 隣接行列を印刷 */ + void print() { + cout << "頂点リスト = "; + printVector(vertices); + cout << "隣接行列 =" << endl; + printVectorMatrix(adjMat); + } +}; + +/* ドライバーコード */ +int main() { + /* 無向グラフを初期化 */ + // 辺の要素は頂点インデックスを表す + 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); + cout << "\n初期化後、グラフは" << endl; + graph.print(); + + /* 辺を追加 */ + // 頂点1、2のインデックスはそれぞれ0、2 + graph.addEdge(0, 2); + cout << "\n辺 1-2 を追加後、グラフは" << endl; + graph.print(); + + /* 辺を削除 */ + // 頂点1、3のインデックスはそれぞれ0、1 + graph.removeEdge(0, 1); + cout << "\n辺 1-3 を削除後、グラフは" << endl; + graph.print(); + + /* 頂点を追加 */ + graph.addVertex(6); + cout << "\n頂点 6 を追加後、グラフは" << endl; + graph.print(); + + /* 頂点を削除 */ + // 頂点3のインデックスは1 + graph.removeVertex(1); + 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 new file mode 100644 index 000000000..0e85a82f4 --- /dev/null +++ b/ja/codes/cpp/chapter_graph/graph_bfs.cpp @@ -0,0 +1,59 @@ +/** + * File: graph_bfs.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" +#include "./graph_adjacency_list.cpp" + +/* 幅優先走査 */ +// 隣接リストを使用してグラフを表現し、指定された頂点のすべての隣接頂点を取得 +vector graphBFS(GraphAdjList &graph, Vertex *startVet) { + // 頂点走査順序 + vector res; + // ハッシュセット、訪問済み頂点を記録するために使用 + unordered_set visited = {startVet}; + // BFSを実装するために使用されるキュー + queue que; + que.push(startVet); + // 頂点vetから開始し、すべての頂点が訪問されるまでループ + while (!que.empty()) { + Vertex *vet = que.front(); + que.pop(); // キューの先頭の頂点をデキュー + res.push_back(vet); // 訪問済み頂点を記録 + // その頂点のすべての隣接頂点を走査 + for (auto adjVet : graph.adjList[vet]) { + if (visited.count(adjVet)) + continue; // すでに訪問済みの頂点をスキップ + que.push(adjVet); // 未訪問の頂点のみをエンキュー + visited.emplace(adjVet); // 頂点を訪問済みとしてマーク + } + } + // 頂点走査順序を返す + return res; +} + +/* ドライバーコード */ +int main() { + /* 無向グラフを初期化 */ + vector v = valsToVets({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + vector> 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(edges); + cout << "\n初期化後、グラフは\n"; + graph.print(); + + /* 幅優先走査 */ + vector res = graphBFS(graph, v[0]); + 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 new file mode 100644 index 000000000..44ed1fc72 --- /dev/null +++ b/ja/codes/cpp/chapter_graph/graph_dfs.cpp @@ -0,0 +1,55 @@ +/** + * File: graph_dfs.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#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); // 頂点を訪問済みとしてマーク + // その頂点のすべての隣接頂点を走査 + for (Vertex *adjVet : graph.adjList[vet]) { + if (visited.count(adjVet)) + continue; // すでに訪問済みの頂点をスキップ + // 隣接頂点を再帰的に訪問 + dfs(graph, visited, res, adjVet); + } +} + +/* 深さ優先走査 */ +// 隣接リストを使用してグラフを表現し、指定された頂点のすべての隣接頂点を取得 +vector graphDFS(GraphAdjList &graph, Vertex *startVet) { + // 頂点走査順序 + vector res; + // ハッシュセット、訪問済み頂点を記録するために使用 + unordered_set visited; + dfs(graph, visited, res, startVet); + return res; +} + +/* ドライバーコード */ +int main() { + /* 無向グラフを初期化 */ + vector v = valsToVets(vector{0, 1, 2, 3, 4, 5, 6}); + vector> 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(edges); + cout << "\n初期化後、グラフは" << endl; + graph.print(); + + /* 深さ優先走査 */ + vector res = graphDFS(graph, v[0]); + 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/coin_change_greedy.cpp b/ja/codes/cpp/chapter_greedy/coin_change_greedy.cpp new file mode 100644 index 000000000..abd112706 --- /dev/null +++ b/ja/codes/cpp/chapter_greedy/coin_change_greedy.cpp @@ -0,0 +1,60 @@ +/** + * File: coin_change_greedy.cpp + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 硬貨両替:貪欲法 */ +int coinChangeGreedy(vector &coins, int amt) { + // 硬貨リストが順序付けされていると仮定 + int i = coins.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; +} + +/* ドライバーコード */ +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; + + // 貪欲法:大域最適解の発見を保証できない + 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; + + // 貪欲法:大域最適解の発見を保証できない + 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; + + 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 new file mode 100644 index 000000000..806722c5a --- /dev/null +++ b/ja/codes/cpp/chapter_greedy/fractional_knapsack.cpp @@ -0,0 +1,56 @@ +/** + * File: fractional_knapsack.cpp + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* アイテム */ +class Item { + public: + int w; // アイテムの重量 + int v; // アイテムの価値 + + Item(int w, int v) : w(w), v(v) { + } +}; + +/* 分数ナップサック:貪欲法 */ +double fractionalKnapsack(vector &wgt, vector &val, int cap) { + // アイテムリストを作成、2つの属性を含む:重量、価値 + vector items; + for (int i = 0; i < wgt.size(); i++) { + items.push_back(Item(wgt[i], val[i])); + } + // 単位価値 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; +} + +/* ドライバーコード */ +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; + + 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 new file mode 100644 index 000000000..b8fbf8a0b --- /dev/null +++ b/ja/codes/cpp/chapter_greedy/max_capacity.cpp @@ -0,0 +1,39 @@ +/** + * File: max_capacity.cpp + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 最大容量:貪欲法 */ +int maxCapacity(vector &ht) { + // i、j を初期化し、配列の両端で分割させる + int i = 0, j = ht.size() - 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; +} + +/* ドライバーコード */ +int main() { + vector ht = {3, 8, 5, 2, 7, 7, 3, 4}; + + // 貪欲アルゴリズム + int res = maxCapacity(ht); + 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 new file mode 100644 index 000000000..b35248656 --- /dev/null +++ b/ja/codes/cpp/chapter_greedy/max_product_cutting.cpp @@ -0,0 +1,39 @@ +/** + * File: max_product_cutting.cpp + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 最大積切断:貪欲法 */ +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)pow(3, a - 1) * 2 * 2; + } + if (b == 2) { + // 余りが 2 の場合、何もしない + return (int)pow(3, a) * 2; + } + // 余りが 0 の場合、何もしない + return (int)pow(3, a); +} + +/* ドライバーコード */ +int main() { + int n = 58; + + // 貪欲アルゴリズム + int res = maxProductCutting(n); + cout << "分割の最大積は " << res << " です" << endl; + + return 0; +} \ 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 new file mode 100644 index 000000000..182c14e18 --- /dev/null +++ b/ja/codes/cpp/chapter_hashing/array_hash_map.cpp @@ -0,0 +1,110 @@ +/** + * File: array_hash_map.cpp + * Created Time: 2022-12-14 + * Author: msk397 (machangxinq@gmail.com) + */ + +#include "../utils/common.hpp" + +/* キー値ペア */ +struct Pair { + public: + int key; + string val; + Pair(int key, string val) { + this->key = key; + this->val = val; + } +}; + +/* 配列実装に基づくハッシュテーブル */ +class ArrayHashMap { + private: + vector buckets; + + public: + ArrayHashMap() { + // 配列を初期化、100個のバケットを含む + buckets = vector(100); + } + + ~ArrayHashMap() { + // メモリを解放 + for (const auto &bucket : buckets) { + delete bucket; + } + buckets.clear(); + } + + /* ハッシュ関数 */ + int hashFunc(int key) { + int index = key % 100; + return index; + } + + /* クエリ操作 */ + string get(int key) { + int index = hashFunc(key); + Pair *pair = buckets[index]; + if (pair == nullptr) + return ""; + return pair->val; + } + + /* 追加操作 */ + void put(int key, string val) { + Pair *pair = new Pair(key, val); + int index = hashFunc(key); + buckets[index] = pair; + } + + /* 削除操作 */ + void remove(int key) { + int index = hashFunc(key); + // メモリを解放してnullptrに設定 + delete buckets[index]; + buckets[index] = nullptr; + } + + /* すべてのキー値ペアを取得 */ + vector pairSet() { + vector pairSet; + for (Pair *pair : buckets) { + if (pair != nullptr) { + pairSet.push_back(pair); + } + } + return pairSet; + } + + /* すべてのキーを取得 */ + vector keySet() { + vector keySet; + for (Pair *pair : buckets) { + if (pair != nullptr) { + keySet.push_back(pair->key); + } + } + return keySet; + } + + /* すべての値を取得 */ + vector valueSet() { + vector valueSet; + for (Pair *pair : buckets) { + if (pair != nullptr) { + valueSet.push_back(pair->val); + } + } + return valueSet; + } + + /* ハッシュテーブルを印刷 */ + void print() { + for (Pair *kv : pairSet()) { + cout << kv->key << " -> " << kv->val << endl; + } + } +}; + +// テストケースはarray_hash_map_test.cppを参照 \ No newline at end of file diff --git a/ja/codes/cpp/chapter_hashing/array_hash_map_test.cpp b/ja/codes/cpp/chapter_hashing/array_hash_map_test.cpp new file mode 100644 index 000000000..c8a7eb676 --- /dev/null +++ b/ja/codes/cpp/chapter_hashing/array_hash_map_test.cpp @@ -0,0 +1,52 @@ +/** + * File: array_hash_map_test.cpp + * Created Time: 2022-12-14 + * Author: msk397 (machangxinq@gmail.com) + */ + +#include "./array_hash_map.cpp" + +/* ドライバーコード */ +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; + map.print(); + + /* クエリ操作 */ + // ハッシュテーブルにキーを入力、値を取得 + string name = map.get(15937); + cout << "\nEnter student ID 15937, found name " << name << endl; + + /* 削除操作 */ + // ハッシュテーブルからキー値ペア(key, value)を削除 + map.remove(10583); + cout << "\nAfter removing 10583, the hash table is\nKey -> Value" << endl; + map.print(); + + /* ハッシュテーブルを走査 */ + cout << "\nTraverse key-value pairs Key->Value" << endl; + for (auto kv : map.pairSet()) { + cout << kv->key << " -> " << kv->val << endl; + } + + cout << "\nIndividually traverse keys Key" << endl; + for (auto key : map.keySet()) { + cout << key << endl; + } + + cout << "\nIndividually traverse values 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 new file mode 100644 index 000000000..086d1ec9b --- /dev/null +++ b/ja/codes/cpp/chapter_hashing/built_in_hash.cpp @@ -0,0 +1,29 @@ +/** + * File: built_in_hash.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* ドライバーコード */ +int main() { + int num = 3; + size_t hashNum = hash()(num); + cout << "The hash value of integer " << num << " is " << hashNum << "\n"; + + bool bol = true; + size_t hashBol = hash()(bol); + cout << "The hash value of boolean " << bol << " is " << hashBol << "\n"; + + double dec = 3.14159; + size_t hashDec = hash()(dec); + cout << "The hash value of decimal " << dec << " is " << hashDec << "\n"; + + string str = "Hello algorithm"; + size_t hashStr = hash()(str); + cout << "The hash value of string " << str << " is " << hashStr << "\n"; + + // C++では、組み込みのstd:hash()は基本データ型のハッシュ値のみを提供 + // 配列やオブジェクトのハッシュ値計算は手動で実装する必要がある +} \ No newline at end of file diff --git a/ja/codes/cpp/chapter_hashing/hash_map.cpp b/ja/codes/cpp/chapter_hashing/hash_map.cpp new file mode 100644 index 000000000..05a9c8018 --- /dev/null +++ b/ja/codes/cpp/chapter_hashing/hash_map.cpp @@ -0,0 +1,46 @@ +/** + * File: hash_map.cpp + * Created Time: 2022-12-14 + * Author: msk397 (machangxinq@gmail.com) + */ + +#include "../utils/common.hpp" + +/* ドライバーコード */ +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; + printHashMap(map); + + /* クエリ操作 */ + // ハッシュテーブルにキーを入力、値を取得 + string name = map[15937]; + cout << "\nEnter student ID 15937, found name " << name << endl; + + /* 削除操作 */ + // ハッシュテーブルからキー値ペア(key, value)を削除 + map.erase(10583); + cout << "\nAfter removing 10583, the hash table is\nKey -> Value" << endl; + printHashMap(map); + + /* ハッシュテーブルを走査 */ + cout << "\nTraverse key-value pairs Key->Value" << endl; + for (auto kv : map) { + cout << kv.first << " -> " << kv.second << endl; + } + cout << "\nIterate through Key->Value using an iterator" << 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 new file mode 100644 index 000000000..c920d05b1 --- /dev/null +++ b/ja/codes/cpp/chapter_hashing/hash_map_chaining.cpp @@ -0,0 +1,150 @@ +/** + * File: hash_map_chaining.cpp + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +#include "./array_hash_map.cpp" + +/* チェイン法ハッシュテーブル */ +class HashMapChaining { + private: + int size; // キー値ペアの数 + int capacity; // ハッシュテーブルの容量 + double loadThres; // 拡張をトリガーする負荷率の閾値 + int extendRatio; // 拡張倍率 + vector> buckets; // バケット配列 + + public: + /* コンストラクタ */ + HashMapChaining() : size(0), capacity(4), loadThres(2.0 / 3.0), extendRatio(2) { + buckets.resize(capacity); + } + + /* デストラクタ */ + ~HashMapChaining() { + for (auto &bucket : buckets) { + for (Pair *pair : bucket) { + // メモリを解放 + delete pair; + } + } + } + + /* ハッシュ関数 */ + int hashFunc(int key) { + return key % capacity; + } + + /* 負荷率 */ + double loadFactor() { + return (double)size / (double)capacity; + } + + /* クエリ操作 */ + string get(int key) { + int index = hashFunc(key); + // バケットを走査、キーが見つかった場合、対応するvalを返却 + for (Pair *pair : buckets[index]) { + if (pair->key == key) { + return pair->val; + } + } + // キーが見つからない場合、空文字列を返却 + return ""; + } + + /* 追加操作 */ + void put(int key, string val) { + // 負荷率が閾値を超えた場合、拡張を実行 + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + // バケットを走査、指定キーに遭遇した場合、対応するvalを更新して返却 + for (Pair *pair : buckets[index]) { + if (pair->key == key) { + pair->val = val; + return; + } + } + // キーが見つからない場合、キー値ペアを末尾に追加 + buckets[index].push_back(new Pair(key, val)); + size++; + } + + /* 削除操作 */ + 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; // メモリを解放 + size--; + return; + } + } + } + + /* ハッシュテーブルを拡張 */ + 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 << "["; + for (Pair *pair : bucket) { + cout << pair->key << " -> " << pair->val << ", "; + } + cout << "]\n"; + } + } +}; + +/* ドライバーコード */ +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; + map.print(); + + /* クエリ操作 */ + // ハッシュテーブルにキーを入力、値を取得 + string name = map.get(13276); + cout << "\nEnter student ID 13276, found name " << name << endl; + + /* 削除操作 */ + // ハッシュテーブルからキー値ペア(key, value)を削除 + map.remove(12836); + cout << "\nAfter removing 12836, the hash table is\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 new file mode 100644 index 000000000..0c96eb6f5 --- /dev/null +++ b/ja/codes/cpp/chapter_hashing/hash_map_open_addressing.cpp @@ -0,0 +1,171 @@ +/** + * File: hash_map_open_addressing.cpp + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +#include "./array_hash_map.cpp" + +/* オープンアドレス法ハッシュテーブル */ +class HashMapOpenAddressing { + private: + int size; // キー値ペアの数 + int capacity = 4; // ハッシュテーブルの容量 + const double loadThres = 2.0 / 3.0; // 拡張をトリガーする負荷率の閾値 + const int extendRatio = 2; // 拡張倍率 + vector buckets; // バケット配列 + Pair *TOMBSTONE = new Pair(-1, "-1"); // 削除マーク + + public: + /* コンストラクタ */ + HashMapOpenAddressing() : size(0), buckets(capacity, nullptr) { + } + + /* デストラクタ */ + ~HashMapOpenAddressing() { + for (Pair *pair : buckets) { + if (pair != nullptr && pair != TOMBSTONE) { + delete pair; + } + } + delete TOMBSTONE; + } + + /* ハッシュ関数 */ + 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] != nullptr) { + // 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] != nullptr && buckets[index] != TOMBSTONE) { + return buckets[index]->val; + } + // キー値ペアが存在しない場合、空文字列を返却 + return ""; + } + + /* 追加操作 */ + void put(int key, string val) { + // 負荷率が閾値を超えた場合、拡張を実行 + if (loadFactor() > loadThres) { + extend(); + } + // keyに対応するバケットインデックスを検索 + int index = findBucket(key); + // キー値ペアが見つかった場合、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に対応するバケットインデックスを検索 + int index = findBucket(key); + // キー値ペアが見つかった場合、削除マークで覆う + if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) { + delete buckets[index]; + buckets[index] = TOMBSTONE; + size--; + } + } + + /* ハッシュテーブルを拡張 */ + 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); + delete pair; + } + } + } + + /* ハッシュテーブルを印刷 */ + void print() { + for (Pair *pair : buckets) { + if (pair == nullptr) { + cout << "nullptr" << endl; + } else if (pair == TOMBSTONE) { + cout << "TOMBSTONE" << endl; + } else { + cout << pair->key << " -> " << pair->val << endl; + } + } + } +}; + +/* ドライバーコード */ +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; + hashmap.print(); + + // クエリ操作 + // ハッシュテーブルにキーを入力、値valを取得 + string name = hashmap.get(13276); + cout << "\nEnter student ID 13276, found name " << name << endl; + + // 削除操作 + // ハッシュテーブルからキー値ペア(key, val)を削除 + hashmap.remove(16750); + cout << "\nAfter removing 16750, the hash table is\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 new file mode 100644 index 000000000..7ae6e7302 --- /dev/null +++ b/ja/codes/cpp/chapter_hashing/simple_hash.cpp @@ -0,0 +1,66 @@ +/** + * File: simple_hash.cpp + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 加算ハッシュ */ +int addHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = (hash + (int)c) % MODULUS; + } + return (int)hash; +} + +/* 乗算ハッシュ */ +int mulHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = (31 * hash + (int)c) % MODULUS; + } + return (int)hash; +} + +/* XORハッシュ */ +int xorHash(string key) { + int hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash ^= (int)c; + } + return hash & MODULUS; +} + +/* 回転ハッシュ */ +int rotHash(string key) { + long long hash = 0; + const int MODULUS = 1000000007; + for (unsigned char c : key) { + hash = ((hash << 4) ^ (hash >> 28) ^ (int)c) % MODULUS; + } + return (int)hash; +} + +/* ドライバーコード */ +int main() { + string key = "Hello algorithm"; + + int hash = addHash(key); + cout << "Additive hash value is " << hash << endl; + + hash = mulHash(key); + cout << "Multiplicative hash value is " << hash << endl; + + hash = xorHash(key); + cout << "XOR hash value is " << hash << endl; + + hash = rotHash(key); + cout << "Rotational hash value is " << hash << endl; + + return 0; +} \ No newline at end of file diff --git a/ja/codes/cpp/chapter_heap/heap.cpp b/ja/codes/cpp/chapter_heap/heap.cpp new file mode 100644 index 000000000..ea787385c --- /dev/null +++ b/ja/codes/cpp/chapter_heap/heap.cpp @@ -0,0 +1,66 @@ +/** + * File: heap.cpp + * Created Time: 2023-01-19 + * Author: LoneRanger(836253168@qq.com) + */ + +#include "../utils/common.hpp" + +void testPush(priority_queue &heap, int val) { + heap.push(val); // 要素をヒープにプッシュ + cout << "\n要素 " << val << " をヒープに追加後" << endl; + printHeap(heap); +} + +void testPop(priority_queue &heap) { + int val = heap.top(); + heap.pop(); + cout << "\nヒープから先頭要素 " << val << " を削除後" << endl; + printHeap(heap); +} + +/* ドライバーコード */ +int main() { + /* ヒープを初期化 */ + // 最小ヒープを初期化 + // priority_queue, greater> minHeap; + // 最大ヒープを初期化 + priority_queue, less> maxHeap; + + 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; + + /* ヒープ先頭の要素をポップ */ + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + + /* ヒープのサイズを取得 */ + int size = maxHeap.size(); + cout << "\nヒープ内の要素数は " << size << endl; + + /* ヒープが空かどうか判定 */ + bool isEmpty = maxHeap.empty(); + cout << "\nヒープが空かどうか " << isEmpty << endl; + + /* リストを入力してヒープを構築 */ + // 時間計算量はO(n)、O(nlogn)ではない + vector input{1, 3, 2, 5, 4}; + priority_queue, greater> minHeap(input.begin(), input.end()); + 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 new file mode 100644 index 000000000..8022f8925 --- /dev/null +++ b/ja/codes/cpp/chapter_heap/my_heap.cpp @@ -0,0 +1,155 @@ +/** + * File: my_heap.cpp + * Created Time: 2023-02-04 + * Author: LoneRanger (836253168@qq.com), what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* 最大ヒープ */ +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; // 整数除算で切り下げ + } + + /* ノードiから上向きにヒープ化を開始 */ + void siftUp(int i) { + while (true) { + // ノードiの親ノードを取得 + int p = parent(i); + // 「ルートノードを超える」または「ノードが修復不要」の場合、ヒープ化を終了 + if (p < 0 || maxHeap[i] <= maxHeap[p]) + break; + // 2つのノードを交換 + swap(maxHeap[i], maxHeap[p]); + // 上向きにループしてヒープ化 + i = p; + } + } + + /* ノード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が最大、またはインデックス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); + } + } + + /* ヒープのサイズを取得 */ + int size() { + return maxHeap.size(); + } + + /* ヒープが空かどうか判定 */ + bool isEmpty() { + return size() == 0; + } + + /* ヒープの先頭要素にアクセス */ + int peek() { + return maxHeap[0]; + } + + /* ヒープに要素をプッシュ */ + void push(int val) { + // ノードを追加 + maxHeap.push_back(val); + // 下から上へヒープ化 + siftUp(size() - 1); + } + + /* 要素がヒープから退出 */ + void pop() { + // 空の処理 + if (isEmpty()) { + throw out_of_range("Heap is empty"); + } + // ルートノードを最も右の葉ノードと交換(最初の要素と最後の要素を交換) + swap(maxHeap[0], maxHeap[size() - 1]); + // ノードを削除 + maxHeap.pop_back(); + // 上から下へヒープ化 + siftDown(0); + } + + /* ヒープを印刷(二分木)*/ + void print() { + cout << "ヒープの配列表現:"; + printVector(maxHeap); + cout << "ヒープの木表現:" << endl; + TreeNode *root = vectorToTree(maxHeap); + printTree(root); + freeMemoryTree(root); + } +}; + +/* ドライバーコード */ +int main() { + /* 最大ヒープを初期化 */ + vector vec{9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2}; + MaxHeap maxHeap(vec); + cout << "\nリストを入力してヒープを構築" << endl; + maxHeap.print(); + + /* ヒープの先頭要素にアクセス */ + int peek = maxHeap.peek(); + cout << "\nヒープの先頭要素は " << peek << endl; + + /* ヒープに要素をプッシュ */ + int val = 7; + maxHeap.push(val); + cout << "\n要素 " << val << " をヒープに追加後" << endl; + maxHeap.print(); + + /* ヒープ先頭の要素をポップ */ + peek = maxHeap.peek(); + maxHeap.pop(); + cout << "\nヒープから先頭要素 " << peek << " を削除後" << endl; + maxHeap.print(); + + /* ヒープのサイズを取得 */ + int size = maxHeap.size(); + cout << "\nヒープ内の要素数は " << size << endl; + + /* ヒープが空かどうか判定 */ + bool isEmpty = maxHeap.isEmpty(); + 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 new file mode 100644 index 000000000..96bd8ea68 --- /dev/null +++ b/ja/codes/cpp/chapter_heap/top_k.cpp @@ -0,0 +1,38 @@ +/** + * File: top_k.cpp + * Created Time: 2023-06-12 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* ヒープを使用して配列内の最大k個の要素を見つける */ +priority_queue, greater> topKHeap(vector &nums, int k) { + // 最小ヒープを初期化 + priority_queue, greater> heap; + // 配列の最初のk個の要素をヒープに入力 + for (int i = 0; i < k; i++) { + heap.push(nums[i]); + } + // k+1番目の要素から、ヒープの長さをkに保つ + for (int i = k; i < nums.size(); i++) { + // 現在の要素がヒープの先頭要素より大きい場合、ヒープの先頭要素を削除し、現在の要素をヒープに入力 + if (nums[i] > heap.top()) { + heap.pop(); + heap.push(nums[i]); + } + } + return heap; +} + +// ドライバーコード +int main() { + vector nums = {1, 7, 6, 3, 2}; + int k = 3; + + priority_queue, greater> res = topKHeap(nums, k); + cout << "最大 " << k << " 個の要素は:"; + printHeap(res); + + return 0; +} \ No newline at end of file diff --git a/ja/codes/cpp/chapter_searching/binary_search.cpp b/ja/codes/cpp/chapter_searching/binary_search.cpp new file mode 100644 index 000000000..ae0761d91 --- /dev/null +++ b/ja/codes/cpp/chapter_searching/binary_search.cpp @@ -0,0 +1,59 @@ +/** + * File: binary_search.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二分探索(両端閉区間) */ +int binarySearch(vector &nums, int target) { + // 両端閉区間[0, n-1]を初期化、すなわちi、jはそれぞれ配列の最初の要素と最後の要素を指す + int i = 0, j = nums.size() - 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(vector &nums, int target) { + // 左閉右開区間[0, n)を初期化、すなわちi、jはそれぞれ配列の最初の要素と最後の要素+1を指す + int i = 0, j = nums.size(); + // 探索区間が空になるまでループ(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; +} + +/* ドライバコード */ +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; + + /* 二分探索(左閉右開区間) */ + index = binarySearchLCRO(nums, target); + 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 new file mode 100644 index 000000000..9c45d53f0 --- /dev/null +++ b/ja/codes/cpp/chapter_searching/binary_search_edge.cpp @@ -0,0 +1,66 @@ +/** + * File: binary_search_edge.cpp + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 挿入ポイントの二分探索(重複要素あり) */ +int binarySearchInsertion(const vector &nums, int target) { + int i = 0, j = nums.size() - 1; // 両端閉区間[0, n-1]を初期化 + while (i <= j) { + int m = i + (j - i) / 2; // 中点インデックスmを計算 + if (nums[m] < target) { + i = m + 1; // ターゲットは区間[m+1, j]にある + } else { + j = m - 1; // ターゲット未満の最初の要素は区間[i, m-1]にある + } + } + // 挿入ポイントiを返す + return i; +} + +/* 最左のターゲットの二分探索 */ +int binarySearchLeftEdge(vector &nums, int target) { + // targetの挿入ポイントを見つけることと等価 + int i = binarySearchInsertion(nums, target); + // targetが見つからなかったため、-1を返す + if (i == nums.size() || nums[i] != target) { + return -1; + } + // targetが見つかったため、インデックスiを返す + return i; +} + +/* 最右のターゲットの二分探索 */ +int binarySearchRightEdge(vector &nums, int target) { + // 最左のtarget + 1を見つけることに変換 + int i = binarySearchInsertion(nums, target + 1); + // jは最右のターゲットを指し、iはtargetより大きい最初の要素を指す + int j = i - 1; + // targetが見つからなかったため、-1を返す + if (j == -1 || nums[j] != target) { + return -1; + } + // targetが見つかったため、インデックスjを返す + return j; +} + +/* ドライバコード */ +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; + index = binarySearchRightEdge(nums, target); + 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 new file mode 100644 index 000000000..f003444de --- /dev/null +++ b/ja/codes/cpp/chapter_searching/binary_search_insertion.cpp @@ -0,0 +1,66 @@ +/** + * File: binary_search_insertion.cpp + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 挿入ポイントの二分探索(重複要素なし) */ +int binarySearchInsertionSimple(vector &nums, int target) { + int i = 0, j = nums.size() - 1; // 両端閉区間[0, n-1]を初期化 + while (i <= j) { + int m = i + (j - i) / 2; // 中点インデックスmを計算 + if (nums[m] < target) { + i = m + 1; // ターゲットは区間[m+1, j]にある + } else if (nums[m] > target) { + j = m - 1; // ターゲットは区間[i, m-1]にある + } else { + return m; // ターゲットが見つかったため、挿入ポイントmを返す + } + } + // ターゲットが見つからなかったため、挿入ポイントiを返す + return i; +} + +/* 挿入ポイントの二分探索(重複要素あり) */ +int binarySearchInsertion(vector &nums, int target) { + int i = 0, j = nums.size() - 1; // 両端閉区間[0, n-1]を初期化 + while (i <= j) { + int m = i + (j - i) / 2; // 中点インデックスmを計算 + if (nums[m] < target) { + i = m + 1; // ターゲットは区間[m+1, j]にある + } else if (nums[m] > target) { + j = m - 1; // ターゲットは区間[i, m-1]にある + } else { + j = m - 1; // ターゲット未満の最初の要素は区間[i, m-1]にある + } + } + // 挿入ポイントiを返す + return i; +} + +/* ドライバコード */ +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; + } + + // 重複要素を含む配列 + 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; + } + + 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 new file mode 100644 index 000000000..6f63deb9f --- /dev/null +++ b/ja/codes/cpp/chapter_searching/hashing_search.cpp @@ -0,0 +1,53 @@ +/** + * File: hashing_search.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* ハッシュ探索(配列) */ +int hashingSearchArray(unordered_map map, int target) { + // ハッシュテーブルのキー:ターゲット要素、値:インデックス + // ハッシュテーブルにこのキーが含まれていない場合、-1を返す + if (map.find(target) == map.end()) + return -1; + return map[target]; +} + +/* ハッシュ探索(連結リスト) */ +ListNode *hashingSearchLinkedList(unordered_map map, int target) { + // ハッシュテーブルのキー:ターゲットノード値、値:ノードオブジェクト + // キーがハッシュテーブルにない場合、nullptrを返す + if (map.find(target) == map.end()) + return nullptr; + return map[target]; +} + +/* ドライバコード */ +int main() { + int target = 3; + + /* ハッシュ探索(配列) */ + vector nums = {1, 5, 3, 2, 4, 7, 5, 9, 10, 8}; + // ハッシュテーブルを初期化 + unordered_map map; + for (int i = 0; i < nums.size(); i++) { + map[nums[i]] = i; // キー:要素、値:インデックス + } + int index = hashingSearchArray(map, target); + cout << "ターゲット要素3のインデックスは " << index << " です" << endl; + + /* ハッシュ探索(連結リスト) */ + ListNode *head = vecToLinkedList(nums); + // ハッシュテーブルを初期化 + unordered_map map1; + while (head != nullptr) { + map1[head->val] = head; // キー:ノード値、値:ノード + head = head->next; + } + ListNode *node = hashingSearchLinkedList(map1, target); + 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 new file mode 100644 index 000000000..77fd76421 --- /dev/null +++ b/ja/codes/cpp/chapter_searching/linear_search.cpp @@ -0,0 +1,49 @@ +/** + * File: linear_search.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 線形探索(配列) */ +int linearSearchArray(vector &nums, int target) { + // 配列を走査 + for (int i = 0; i < nums.size(); i++) { + // ターゲット要素が見つかったため、そのインデックスを返す + if (nums[i] == target) + return i; + } + // ターゲット要素が見つからなかったため、-1を返す + return -1; +} + +/* 線形探索(連結リスト) */ +ListNode *linearSearchLinkedList(ListNode *head, int target) { + // リストを走査 + while (head != nullptr) { + // ターゲットノードが見つかった場合、それを返す + if (head->val == target) + return head; + head = head->next; + } + // ターゲットノードが見つからない場合、nullptrを返す + return nullptr; +} + +/* ドライバコード */ +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; + + /* 連結リストで線形探索を実行 */ + ListNode *head = vecToLinkedList(nums); + ListNode *node = linearSearchLinkedList(head, target); + 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 new file mode 100644 index 000000000..3be0d0138 --- /dev/null +++ b/ja/codes/cpp/chapter_searching/two_sum.cpp @@ -0,0 +1,54 @@ +/** + * File: two_sum.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 方法一:ブルートフォース列挙 */ +vector twoSumBruteForce(vector &nums, int target) { + int size = nums.size(); + // 二重ループ、時間計算量は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 {}; +} + +/* 方法二:補助ハッシュテーブル */ +vector twoSumHashTable(vector &nums, int target) { + int size = nums.size(); + // 補助ハッシュテーブル、空間計算量はO(n) + unordered_map dic; + // 単層ループ、時間計算量はO(n) + for (int i = 0; i < size; i++) { + if (dic.find(target - nums[i]) != dic.end()) { + return {dic[target - nums[i]], i}; + } + dic.emplace(nums[i], i); + } + return {}; +} + +/* ドライバコード */ +int main() { + // ======= テストケース ======= + vector nums = {2, 7, 11, 15}; + int target = 13; + + // ====== ドライバコード ====== + // 方法一 + vector res = twoSumBruteForce(nums, target); + cout << "方法一 res = "; + printVector(res); + // 方法二 + res = twoSumHashTable(nums, target); + cout << "方法二 res = "; + printVector(res); + + return 0; +} \ 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 new file mode 100644 index 000000000..326ecf23b --- /dev/null +++ b/ja/codes/cpp/chapter_sorting/bubble_sort.cpp @@ -0,0 +1,56 @@ +/** + * File: bubble_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* バブルソート */ +void bubbleSort(vector &nums) { + // 外側ループ:未ソート範囲は[0, i] + for (int i = nums.size() - 1; 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を使用 + swap(nums[j], nums[j + 1]); + } + } + } +} + +/* バブルソート(フラグ最適化版)*/ +void bubbleSortWithFlag(vector &nums) { + // 外側ループ:未ソート範囲は[0, i] + for (int i = nums.size() - 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]を交換 + // ここではstdのswapを使用 + swap(nums[j], nums[j + 1]); + flag = true; // 交換された要素を記録 + } + } + if (!flag) + break; // この回の「バブリング」で要素が交換されなかった場合、終了 + } +} + +/* ドライバコード */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + bubbleSort(nums); + cout << "バブルソート後、nums = "; + printVector(nums); + + vector nums1 = {4, 1, 3, 1, 5, 2}; + bubbleSortWithFlag(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 new file mode 100644 index 000000000..83f941709 --- /dev/null +++ b/ja/codes/cpp/chapter_sorting/bucket_sort.cpp @@ -0,0 +1,44 @@ +/** + * File: bucket_sort.cpp + * Created Time: 2023-03-30 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* バケットソート */ +void bucketSort(vector &nums) { + // k = n/2個のバケットを初期化、各バケットに2つの要素を割り当てることを期待 + int k = nums.size() / 2; + vector> buckets(k); + // 1. 配列要素を各バケットに分配 + for (float num : nums) { + // 入力データ範囲は[0, 1)、num * kを使用してインデックス範囲[0, k-1]にマップ + int i = num * k; + // bucket_idxバケットに数値を追加 + buckets[i].push_back(num); + } + // 2. 各バケットをソート + for (vector &bucket : buckets) { + // 組み込みソート関数を使用、他のソートアルゴリズムに置き換えることも可能 + sort(bucket.begin(), bucket.end()); + } + // 3. バケットを走査して結果をマージ + int i = 0; + for (vector &bucket : buckets) { + for (float num : bucket) { + nums[i++] = num; + } + } +} + +/* ドライバコード */ +int main() { + // 入力データが浮動小数点数、範囲[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 = "; + 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 new file mode 100644 index 000000000..20391b01f --- /dev/null +++ b/ja/codes/cpp/chapter_sorting/counting_sort.cpp @@ -0,0 +1,77 @@ +/** + * File: counting_sort.cpp + * Created Time: 2023-03-17 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* カウントソート */ +// 簡単な実装、オブジェクトのソートには使用できない +void countingSortNaive(vector &nums) { + // 1. 配列の最大要素mを統計 + int m = 0; + for (int num : nums) { + m = max(m, num); + } + // 2. 各数字の出現回数を統計 + // counter[num]はnumの出現回数を表す + vector counter(m + 1, 0); + for (int num : 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(vector &nums) { + // 1. 配列の最大要素mを統計 + int m = 0; + for (int num : nums) { + m = max(m, num); + } + // 2. 各数字の出現回数を統計 + // counter[num]はnumの出現回数を表す + vector counter(m + 1, 0); + for (int num : 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.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で元の配列numsを上書き + nums = res; +} + +/* ドライバコード */ +int main() { + vector nums = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + countingSortNaive(nums); + cout << "カウントソート(オブジェクトソート不可)後、nums = "; + printVector(nums); + + vector nums1 = {1, 0, 1, 2, 0, 4, 0, 2, 2, 4}; + countingSort(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 new file mode 100644 index 000000000..edfdb6e78 --- /dev/null +++ b/ja/codes/cpp/chapter_sorting/heap_sort.cpp @@ -0,0 +1,54 @@ +/** + * File: heap_sort.cpp + * Created Time: 2023-05-26 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* ヒープの長さはn、ノードiから上から下へヒープ化を開始 */ +void siftDown(vector &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; + } + // 二つのノードを交換 + 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回繰り返す + for (int i = nums.size() - 1; i > 0; --i) { + // ルートノードを最右葉ノードと交換(最初の要素を最後の要素と交換) + swap(nums[0], nums[i]); + // ルートノードから上から下へヒープ化を開始 + siftDown(nums, i, 0); + } +} + +/* ドライバコード */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + heapSort(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 new file mode 100644 index 000000000..031a28aac --- /dev/null +++ b/ja/codes/cpp/chapter_sorting/insertion_sort.cpp @@ -0,0 +1,31 @@ +/** + * File: insertion_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 挿入ソート */ +void insertionSort(vector &nums) { + // 外側ループ:ソート済み範囲は[0, i-1] + for (int i = 1; i < nums.size(); 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]を一つ右に移動 + j--; + } + nums[j + 1] = base; // baseを正しい位置に代入 + } +} + +/* ドライバコード */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + insertionSort(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 new file mode 100644 index 000000000..43bc1f7f0 --- /dev/null +++ b/ja/codes/cpp/chapter_sorting/merge_sort.cpp @@ -0,0 +1,58 @@ +/** + * File: merge_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 左サブ配列と右サブ配列をマージ */ +void merge(vector &nums, int left, int mid, int right) { + // 左サブ配列の区間は[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の対応する区間にコピー + for (k = 0; k < tmp.size(); k++) { + nums[left + k] = tmp[k]; + } +} + +/* マージソート */ +void mergeSort(vector &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); +} + +/* ドライバコード */ +int main() { + /* マージソート */ + vector nums = {7, 3, 2, 6, 0, 1, 5, 4}; + mergeSort(nums, 0, nums.size() - 1); + 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 new file mode 100644 index 000000000..66a43a030 --- /dev/null +++ b/ja/codes/cpp/chapter_sorting/quick_sort.cpp @@ -0,0 +1,166 @@ +/** + * File: quick_sort.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* クイックソートクラス */ +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]をピボットとして使用 + 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); // これら二つの要素を交換 + } + swap(nums, i, left); // ピボットを二つのサブ配列の境界に交換 + return i; // ピボットのインデックスを返す + } + + public: + /* クイックソート */ + static void quickSort(vector &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 { + private: + /* 要素を交換 */ + static void swap(vector &nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 三つの候補要素の中央値を選択 */ + 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の間 + if ((m <= l && l <= r) || (r <= l && l <= m)) + return left; // lはmとrの間 + return right; + } + + /* 分割(三つの中央値) */ + static int partition(vector &nums, int left, int right) { + // 三つの候補要素の中央値を選択 + 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); // これら二つの要素を交換 + } + swap(nums, i, left); // ピボットを二つのサブ配列の境界に交換 + return i; // ピボットのインデックスを返す + } + + public: + /* クイックソート */ + static void quickSort(vector &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 { + 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]をピボットとして使用 + 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); // これら二つの要素を交換 + } + swap(nums, i, left); // ピボットを二つのサブ配列の境界に交換 + return i; // ピボットのインデックスを返す + } + + public: + /* クイックソート(末尾再帰最適化) */ + static void quickSort(vector &nums, int left, int right) { + // サブ配列の長さが1の時終了 + while (left < right) { + // 分割操作 + int pivot = partition(nums, left, right); + // 二つのサブ配列のうち短い方でクイックソートを実行 + 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] + } + } + } +}; + +/* ドライバコード */ +int main() { + /* クイックソート */ + vector nums{2, 4, 1, 0, 3, 5}; + QuickSort::quickSort(nums, 0, nums.size() - 1); + cout << "クイックソート後、nums = "; + printVector(nums); + + /* クイックソート(中央値ピボット最適化) */ + vector nums1 = {2, 4, 1, 0, 3, 5}; + QuickSortMedian::quickSort(nums1, 0, nums1.size() - 1); + cout << "クイックソート(中央値ピボット最適化)完了、nums = "; + printVector(nums1); + + /* クイックソート(末尾再帰最適化) */ + vector nums2 = {2, 4, 1, 0, 3, 5}; + QuickSortTailCall::quickSort(nums2, 0, nums2.size() - 1); + 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 new file mode 100644 index 000000000..1855e0024 --- /dev/null +++ b/ja/codes/cpp/chapter_sorting/radix_sort.cpp @@ -0,0 +1,65 @@ +/** + * File: radix_sort.cpp + * Created Time: 2023-03-26 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 要素numのk番目の桁を取得、exp = 10^(k-1) */ +int digit(int num, int exp) { + // kの代わりにexpを渡すことで、ここで繰り返される高価な冪乗計算を避けることができる + return (num / exp) % 10; +} + +/* カウントソート(numsのk番目の桁に基づく) */ +void countingSortDigit(vector &nums, int exp) { + // 10進数の桁範囲は0~9なので、長さ10のバケット配列が必要 + vector counter(10, 0); + int n = nums.size(); + // 数字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に配置 + 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減らす + } + // 結果で元の配列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 = 1 -> exp = 1 + // k = 2 -> exp = 10 + // つまり、exp = 10^(k-1) + countingSortDigit(nums, exp); +} + +/* ドライバコード */ +int main() { + // 基数ソート + vector nums = {10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996}; + radixSort(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 new file mode 100644 index 000000000..ebdc065a1 --- /dev/null +++ b/ja/codes/cpp/chapter_sorting/selection_sort.cpp @@ -0,0 +1,34 @@ +/** + * File: selection_sort.cpp + * Created Time: 2023-05-23 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 選択ソート */ +void selectionSort(vector &nums) { + int n = nums.size(); + // 外側ループ:未ソート範囲は[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]); + } +} + +/* ドライバコード */ +int main() { + vector nums = {4, 1, 3, 1, 5, 2}; + selectionSort(nums); + + cout << "選択ソート後、nums = "; + printVector(nums); + + return 0; +} \ No newline at end of file diff --git a/ja/codes/cpp/chapter_stack_and_queue/array_deque.cpp b/ja/codes/cpp/chapter_stack_and_queue/array_deque.cpp new file mode 100644 index 000000000..36500745a --- /dev/null +++ b/ja/codes/cpp/chapter_stack_and_queue/array_deque.cpp @@ -0,0 +1,156 @@ +/** + * File: array_deque.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 循環配列に基づく両端キュークラス */ +class ArrayDeque { + private: + vector nums; // 両端キューの要素を格納する配列 + int front; // 先頭ポインタ、先頭要素を指す + int queSize; // 両端キューの長さ + + public: + /* コンストラクタ */ + ArrayDeque(int capacity) { + nums.resize(capacity); + front = queSize = 0; + } + + /* 両端キューの容量を取得 */ + int capacity() { + return nums.size(); + } + + /* 両端キューの長さを取得 */ + 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()) { + cout << "Double-ended queue is full" << endl; + return; + } + // 先頭ポインタを1つ左に移動 + // 剰余演算でfrontが配列の先頭を越えて末尾に戻ることを実現 + front = index(front - 1); + // numを先頭に追加 + nums[front] = num; + queSize++; + } + + /* 末尾エンキュー */ + void pushLast(int num) { + if (queSize == capacity()) { + cout << "Double-ended queue is full" << endl; + return; + } + // 末尾ポインタを計算、末尾インデックス + 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 out_of_range("Double-ended queue is empty"); + return nums[front]; + } + + /* 末尾要素にアクセス */ + int peekLast() { + if (isEmpty()) + throw out_of_range("Double-ended queue is empty"); + // 末尾要素のインデックスを計算 + 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)]; + } + return res; + } +}; + +/* ドライバーコード */ +int main() { + /* 両端キューを初期化 */ + ArrayDeque *deque = new ArrayDeque(10); + deque->pushLast(3); + deque->pushLast(2); + deque->pushLast(5); + cout << "Double-ended queue deque = "; + printVector(deque->toVector()); + + /* 要素にアクセス */ + int peekFirst = deque->peekFirst(); + cout << "Front element peekFirst = " << peekFirst << endl; + int peekLast = deque->peekLast(); + cout << "Back element peekLast = " << peekLast << endl; + + /* 要素エンキュー */ + deque->pushLast(4); + cout << "Element 4 enqueued at the tail, deque = "; + printVector(deque->toVector()); + deque->pushFirst(1); + cout << "Element 1 enqueued at the head, deque = "; + printVector(deque->toVector()); + + /* 要素デキュー */ + int popLast = deque->popLast(); + cout << "Deque tail element = " << popLast << ", after dequeuing from the tail"; + printVector(deque->toVector()); + int popFirst = deque->popFirst(); + cout << "Deque front element = " << popFirst << ", after dequeuing from the front"; + printVector(deque->toVector()); + + /* 両端キューの長さを取得 */ + int size = deque->size(); + cout << "Length of the double-ended queue size = " << size << endl; + + /* 両端キューが空かどうかを判定 */ + bool isEmpty = deque->isEmpty(); + cout << "Is the double-ended queue empty = " << 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 new file mode 100644 index 000000000..5489b0371 --- /dev/null +++ b/ja/codes/cpp/chapter_stack_and_queue/array_queue.cpp @@ -0,0 +1,129 @@ +/** + * File: array_queue.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 循環配列に基づくキュークラス */ +class ArrayQueue { + private: + int *nums; // キュー要素を格納する配列 + int front; // 先頭ポインタ、先頭要素を指す + int queSize; // キューの長さ + int queCapacity; // キューの容量 + + public: + ArrayQueue(int capacity) { + // 配列を初期化 + nums = new int[capacity]; + queCapacity = capacity; + front = queSize = 0; + } + + ~ArrayQueue() { + delete[] nums; + } + + /* キューの容量を取得 */ + int capacity() { + return queCapacity; + } + + /* キューの長さを取得 */ + int size() { + return queSize; + } + + /* キューが空かどうかを判定 */ + bool isEmpty() { + return size() == 0; + } + + /* エンキュー */ + void push(int num) { + if (queSize == queCapacity) { + cout << "Queue is full" << endl; + return; + } + // 末尾ポインタを計算、末尾インデックス + 1を指す + // 剰余演算を使用して末尾ポインタが配列の末尾から先頭に戻るようにラップ + int rear = (front + queSize) % queCapacity; + // numを末尾に追加 + nums[rear] = num; + queSize++; + } + + /* デキュー */ + int pop() { + int num = peek(); + // 先頭ポインタを1つ後ろに移動、末尾を超えた場合は配列の先頭に戻る + front = (front + 1) % queCapacity; + queSize--; + return num; + } + + /* 先頭要素にアクセス */ + int peek() { + if (isEmpty()) + throw out_of_range("Queue is empty"); + return nums[front]; + } + + /* 配列をVectorに変換して返却 */ + vector toVector() { + // 有効な長さ範囲内の要素のみを変換 + vector arr(queSize); + for (int i = 0, j = front; i < queSize; i++, j++) { + arr[i] = nums[j % queCapacity]; + } + return arr; + } +}; + +/* ドライバーコード */ +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 = "; + printVector(queue->toVector()); + + /* 先頭要素にアクセス */ + int peek = queue->peek(); + cout << "Front element peek = " << peek << endl; + + /* 要素デキュー */ + peek = queue->pop(); + cout << "Element dequeued = " << peek << ", after dequeuing"; + printVector(queue->toVector()); + + /* キューの長さを取得 */ + int size = queue->size(); + cout << "Length of the queue size = " << size << endl; + + /* キューが空かどうかを判定 */ + bool empty = queue->isEmpty(); + cout << "Is the queue empty = " << empty << endl; + + /* 循環配列をテスト */ + for (int i = 0; i < 10; i++) { + queue->push(i); + queue->pop(); + cout << "After the " << i << "th round of enqueueing + dequeuing, 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 new file mode 100644 index 000000000..ffde40e51 --- /dev/null +++ b/ja/codes/cpp/chapter_stack_and_queue/array_stack.cpp @@ -0,0 +1,85 @@ +/** + * File: array_stack.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* 配列に基づくスタッククラス */ +class ArrayStack { + private: + vector stack; + + public: + /* スタックの長さを取得 */ + int size() { + return stack.size(); + } + + /* スタックが空かどうかを判定 */ + bool isEmpty() { + return stack.size() == 0; + } + + /* プッシュ */ + void push(int num) { + stack.push_back(num); + } + + /* ポップ */ + int pop() { + int num = top(); + stack.pop_back(); + return num; + } + + /* スタックトップ要素にアクセス */ + int top() { + if (isEmpty()) + throw out_of_range("Stack is empty"); + return stack.back(); + } + + /* Vectorを返却 */ + vector toVector() { + return stack; + } +}; + +/* ドライバーコード */ +int main() { + /* スタックを初期化 */ + ArrayStack *stack = new ArrayStack(); + + /* 要素プッシュ */ + stack->push(1); + stack->push(3); + stack->push(2); + stack->push(5); + stack->push(4); + cout << "Stack stack = "; + printVector(stack->toVector()); + + /* スタックトップ要素にアクセス */ + int top = stack->top(); + cout << "Top element of the stack top = " << top << endl; + + /* 要素ポップ */ + top = stack->pop(); + cout << "Element popped from the stack = " << top << ", after popping"; + printVector(stack->toVector()); + + /* スタックの長さを取得 */ + int size = stack->size(); + cout << "Length of the stack size = " << size << endl; + + /* 空かどうかを判定 */ + bool empty = stack->isEmpty(); + cout << "Is the stack empty = " << 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 new file mode 100644 index 000000000..135d025e7 --- /dev/null +++ b/ja/codes/cpp/chapter_stack_and_queue/deque.cpp @@ -0,0 +1,46 @@ +/** + * File: deque.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* ドライバーコード */ +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 = "; + printDeque(deque); + + /* 要素にアクセス */ + int front = deque.front(); + cout << "Front element of the queue front = " << front << endl; + int back = deque.back(); + cout << "Back element of the queue back = " << back << endl; + + /* 要素デキュー */ + deque.pop_front(); + cout << "Front element dequeued = " << front << ", after dequeuing from the front"; + printDeque(deque); + deque.pop_back(); + cout << "Back element dequeued = " << back << ", after dequeuing from the back"; + printDeque(deque); + + /* 両端キューの長さを取得 */ + int size = deque.size(); + cout << "Length of the double-ended queue size = " << size << endl; + + /* 両端キューが空かどうかを判定 */ + bool empty = deque.empty(); + cout << "Is the double-ended queue empty = " << 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 new file mode 100644 index 000000000..982dabd14 --- /dev/null +++ b/ja/codes/cpp/chapter_stack_and_queue/linkedlist_deque.cpp @@ -0,0 +1,194 @@ +/** + * File: linkedlist_deque.cpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 双方向連結リストノード */ +struct DoublyListNode { + int val; // ノードの値 + DoublyListNode *next; // 後続ノードへのポインタ + DoublyListNode *prev; // 前続ノードへのポインタ + DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) { + } +}; + +/* 双方向連結リストに基づく両端キュークラス */ +class LinkedListDeque { + private: + DoublyListNode *front, *rear; // 先頭ノードfront、末尾ノードrear + int queSize = 0; // 両端キューの長さ + + public: + /* コンストラクタ */ + LinkedListDeque() : front(nullptr), rear(nullptr) { + } + + /* デストラクタ */ + ~LinkedListDeque() { + // 連結リストを走査、ノードを削除、メモリを解放 + DoublyListNode *pre, *cur = front; + while (cur != nullptr) { + pre = cur; + cur = cur->next; + delete pre; + } + } + + /* 両端キューの長さを取得 */ + int size() { + return queSize; + } + + /* 両端キューが空かどうかを判定 */ + bool isEmpty() { + return size() == 0; + } + + /* エンキュー操作 */ + void push(int num, bool isFront) { + DoublyListNode *node = new DoublyListNode(num); + // リストが空の場合、frontとrearの両方をnodeに向ける + if (isEmpty()) + front = rear = node; + // 先頭エンキュー操作 + else if (isFront) { + // ノードをリストの先頭に追加 + front->prev = node; + node->next = front; + front = node; // 先頭ノードを更新 + // 末尾エンキュー操作 + } else { + // ノードをリストの末尾に追加 + 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) { + if (isEmpty()) + throw out_of_range("Queue is empty"); + int val; + // 先頭デキュー操作 + if (isFront) { + val = front->val; // 先頭ノードの値を一時保存 + // 先頭ノードを削除 + DoublyListNode *fNext = front->next; + if (fNext != nullptr) { + fNext->prev = nullptr; + front->next = nullptr; + } + delete front; + front = fNext; // 先頭ノードを更新 + // 末尾デキュー操作 + } else { + val = rear->val; // 末尾ノードの値を一時保存 + // 末尾ノードを削除 + DoublyListNode *rPrev = rear->prev; + if (rPrev != nullptr) { + rPrev->next = nullptr; + rear->prev = nullptr; + } + delete rear; + rear = rPrev; // 末尾ノードを更新 + } + 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"); + return front->val; + } + + /* 末尾要素にアクセス */ + int peekLast() { + if (isEmpty()) + throw out_of_range("Double-ended queue is empty"); + return rear->val; + } + + /* 印刷用に配列を返却 */ + vector toVector() { + DoublyListNode *node = front; + vector res(size()); + for (int i = 0; i < res.size(); i++) { + res[i] = node->val; + node = node->next; + } + return res; + } +}; + +/* ドライバーコード */ +int main() { + /* 両端キューを初期化 */ + LinkedListDeque *deque = new LinkedListDeque(); + deque->pushLast(3); + deque->pushLast(2); + deque->pushLast(5); + cout << "Double-ended queue deque = "; + printVector(deque->toVector()); + + /* 要素にアクセス */ + int peekFirst = deque->peekFirst(); + cout << "Front element peekFirst = " << peekFirst << endl; + int peekLast = deque->peekLast(); + cout << "Back element peekLast = " << peekLast << endl; + + /* 要素エンキュー */ + deque->pushLast(4); + cout << "Element 4 rear enqueued, deque ="; + printVector(deque->toVector()); + deque->pushFirst(1); + cout << "Element 1 enqueued at the head, deque = "; + printVector(deque->toVector()); + + /* 要素デキュー */ + int popLast = deque->popLast(); + cout << "Deque tail element = " << popLast << ", after dequeuing from the tail"; + printVector(deque->toVector()); + int popFirst = deque->popFirst(); + cout << "Deque front element = " << popFirst << ", after dequeuing from the front"; + printVector(deque->toVector()); + + /* 両端キューの長さを取得 */ + int size = deque->size(); + cout << "Length of the double-ended queue size = " << size << endl; + + /* 両端キューが空かどうかを判定 */ + bool isEmpty = deque->isEmpty(); + cout << "Is the double-ended queue empty = " << 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 new file mode 100644 index 000000000..18cfe7371 --- /dev/null +++ b/ja/codes/cpp/chapter_stack_and_queue/linkedlist_queue.cpp @@ -0,0 +1,120 @@ +/** + * File: linkedlist_queue.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 連結リストに基づくキュークラス */ +class LinkedListQueue { + private: + ListNode *front, *rear; // 先頭ノードfront、末尾ノードrear + int queSize; + + public: + LinkedListQueue() { + front = nullptr; + rear = nullptr; + queSize = 0; + } + + ~LinkedListQueue() { + // 連結リストを走査、ノードを削除、メモリを解放 + freeMemoryLinkedList(front); + } + + /* キューの長さを取得 */ + int size() { + return queSize; + } + + /* キューが空かどうかを判定 */ + bool isEmpty() { + return queSize == 0; + } + + /* エンキュー */ + void push(int num) { + // 末尾ノードの後ろにnumを追加 + ListNode *node = new ListNode(num); + // キューが空の場合、先頭と末尾ノードの両方をそのノードに向ける + if (front == nullptr) { + front = node; + rear = node; + } + // キューが空でない場合、そのノードを末尾ノードの後ろに追加 + else { + rear->next = node; + rear = node; + } + queSize++; + } + + /* デキュー */ + int pop() { + int num = peek(); + // 先頭ノードを削除 + ListNode *tmp = front; + front = front->next; + // メモリを解放 + delete tmp; + queSize--; + return num; + } + + /* 先頭要素にアクセス */ + int peek() { + if (size() == 0) + throw out_of_range("Queue is empty"); + return front->val; + } + + /* 連結リストをVectorに変換して返却 */ + vector toVector() { + ListNode *node = front; + vector res(size()); + for (int i = 0; i < res.size(); i++) { + res[i] = node->val; + node = node->next; + } + return res; + } +}; + +/* ドライバーコード */ +int main() { + /* キューを初期化 */ + LinkedListQueue *queue = new LinkedListQueue(); + + /* 要素エンキュー */ + queue->push(1); + queue->push(3); + queue->push(2); + queue->push(5); + queue->push(4); + cout << "Queue queue = "; + printVector(queue->toVector()); + + /* 先頭要素にアクセス */ + int peek = queue->peek(); + cout << "Front element peek = " << peek << endl; + + /* 要素デキュー */ + peek = queue->pop(); + cout << "Element dequeued = " << peek << ", after dequeuing"; + printVector(queue->toVector()); + + /* キューの長さを取得 */ + int size = queue->size(); + cout << "Length of the queue size = " << size << endl; + + /* キューが空かどうかを判定 */ + bool empty = queue->isEmpty(); + cout << "Is the queue empty = " << 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 new file mode 100644 index 000000000..ad047849a --- /dev/null +++ b/ja/codes/cpp/chapter_stack_and_queue/linkedlist_stack.cpp @@ -0,0 +1,109 @@ +/** + * File: linkedlist_stack.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* 連結リストに基づくスタッククラス */ +class LinkedListStack { + private: + ListNode *stackTop; // 先頭ノードをスタックトップとして使用 + int stkSize; // スタックの長さ + + public: + LinkedListStack() { + stackTop = nullptr; + stkSize = 0; + } + + ~LinkedListStack() { + // 連結リストを走査、ノードを削除、メモリを解放 + freeMemoryLinkedList(stackTop); + } + + /* スタックの長さを取得 */ + int size() { + return stkSize; + } + + /* スタックが空かどうかを判定 */ + bool isEmpty() { + return size() == 0; + } + + /* プッシュ */ + void push(int num) { + ListNode *node = new ListNode(num); + node->next = stackTop; + stackTop = node; + stkSize++; + } + + /* ポップ */ + int pop() { + 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"); + return stackTop->val; + } + + /* リストを配列に変換して返却 */ + vector toVector() { + ListNode *node = stackTop; + vector res(size()); + for (int i = res.size() - 1; i >= 0; i--) { + res[i] = node->val; + node = node->next; + } + return res; + } +}; + +/* ドライバーコード */ +int main() { + /* スタックを初期化 */ + LinkedListStack *stack = new LinkedListStack(); + + /* 要素プッシュ */ + stack->push(1); + stack->push(3); + stack->push(2); + stack->push(5); + stack->push(4); + cout << "Stack stack = "; + printVector(stack->toVector()); + + /* スタックトップ要素にアクセス */ + int top = stack->top(); + cout << "Top element of the stack top = " << top << endl; + + /* 要素ポップ */ + top = stack->pop(); + cout << "Element popped from the stack = " << top << ", after popping"; + printVector(stack->toVector()); + + /* スタックの長さを取得 */ + int size = stack->size(); + cout << "Length of the stack size = " << size << endl; + + /* 空かどうかを判定 */ + bool empty = stack->isEmpty(); + cout << "Is the stack empty = " << 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 new file mode 100644 index 000000000..bd97b14d8 --- /dev/null +++ b/ja/codes/cpp/chapter_stack_and_queue/queue.cpp @@ -0,0 +1,41 @@ +/** + * File: queue.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* ドライバーコード */ +int main() { + /* キューを初期化 */ + queue queue; + + /* 要素エンキュー */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + queue.push(4); + cout << "Queue queue = "; + printQueue(queue); + + /* 先頭要素にアクセス */ + int front = queue.front(); + cout << "Front element of the queue front = " << front << endl; + + /* 要素デキュー */ + queue.pop(); + cout << "Element dequeued = " << front << ", after dequeuing"; + printQueue(queue); + + /* キューの長さを取得 */ + int size = queue.size(); + cout << "Length of the queue size = " << size << endl; + + /* キューが空かどうかを判定 */ + bool empty = queue.empty(); + cout << "Is the queue empty = " << 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 new file mode 100644 index 000000000..9e2bceb8c --- /dev/null +++ b/ja/codes/cpp/chapter_stack_and_queue/stack.cpp @@ -0,0 +1,41 @@ +/** + * File: stack.cpp + * Created Time: 2022-11-28 + * Author: qualifier1024 (2539244001@qq.com) + */ + +#include "../utils/common.hpp" + +/* ドライバーコード */ +int main() { + /* スタックを初期化 */ + stack stack; + + /* 要素プッシュ */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + cout << "Stack stack = "; + printStack(stack); + + /* スタックトップ要素にアクセス */ + int top = stack.top(); + cout << "Top element of the stack top = " << top << endl; + + /* 要素ポップ */ + stack.pop(); // 戻り値なし + cout << "Element popped from the stack = " << top << ", after popping"; + printStack(stack); + + /* スタックの長さを取得 */ + int size = stack.size(); + cout << "Length of the stack size = " << size << endl; + + /* 空かどうかを判定 */ + bool empty = stack.empty(); + cout << "Is the stack empty = " << empty << endl; + + return 0; +} \ 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 new file mode 100644 index 000000000..6e3dd2fe3 --- /dev/null +++ b/ja/codes/cpp/chapter_tree/array_binary_tree.cpp @@ -0,0 +1,137 @@ +/** + * File: array_binary_tree.cpp + * Created Time: 2023-07-19 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 配列ベースの二分木クラス */ +class ArrayBinaryTree { + public: + /* コンストラクタ */ + ArrayBinaryTree(vector arr) { + tree = arr; + } + + /* リストの容量 */ + int size() { + return tree.size(); + } + + /* インデックス i のノードの値を取得 */ + int val(int i) { + // インデックスが範囲外の場合、INT_MAX を返す(null を表す) + if (i < 0 || i >= size()) + return INT_MAX; + 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; + } + + /* レベル順走査 */ + vector levelOrder() { + vector res; + // 配列を走査 + for (int i = 0; i < size(); i++) { + if (val(i) != INT_MAX) + res.push_back(val(i)); + } + return res; + } + + /* 前順走査 */ + vector preOrder() { + vector res; + dfs(0, "pre", res); + return res; + } + + /* 中順走査 */ + vector inOrder() { + vector res; + dfs(0, "in", res); + return res; + } + + /* 後順走査 */ + vector postOrder() { + vector res; + dfs(0, "post", res); + return res; + } + + 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); + // 中順走査 + if (order == "in") + res.push_back(val(i)); + dfs(right(i), order, res); + // 後順走査 + if (order == "post") + res.push_back(val(i)); + } +}; + +/* ドライバーコード */ +int main() { + // 二分木を初期化 + // INT_MAX を使用して空の位置 nullptr を表す + 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"; + printVector(arr); + 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"; + + // 木を走査 + vector res = abt.levelOrder(); + cout << "\nレベル順走査は:"; + printVector(res); + res = abt.preOrder(); + cout << "前順走査は:"; + printVector(res); + res = abt.inOrder(); + cout << "中順走査は:"; + printVector(res); + res = abt.postOrder(); + 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 new file mode 100644 index 000000000..743541db0 --- /dev/null +++ b/ja/codes/cpp/chapter_tree/avl_tree.cpp @@ -0,0 +1,233 @@ +/** + * File: avl_tree.cpp + * Created Time: 2023-02-03 + * Author: what-is-me (whatisme@outlook.jp) + */ + +#include "../utils/common.hpp" + +/* AVL木 */ +class AVLTree { + private: + /* ノードの高さを更新 */ + void updateHeight(TreeNode *node) { + // ノードの高さ = 最も高い部分木の高さ + 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->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 _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. 挿入位置を見つけてノードを挿入 */ + 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; + } + + /* ノードを再帰的に削除(ヘルパーメソッド) */ + TreeNode *removeHelper(TreeNode *node, int val) { + if (node == nullptr) + return nullptr; + /* 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 == nullptr || node->right == nullptr) { + TreeNode *child = node->left != nullptr ? node->left : node->right; + // 子ノード数 = 0、ノードを削除して戻る + if (child == nullptr) { + delete node; + return nullptr; + } + // 子ノード数 = 1、ノードを削除 + else { + delete node; + node = child; + } + } else { + // 子ノード数 = 2、中順走査の次のノードを削除し、現在のノードと置き換える + TreeNode *temp = node->right; + while (temp->left != nullptr) { + 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; + } + + public: + TreeNode *root; // ルートノード + + /* ノードの高さを取得 */ + int height(TreeNode *node) { + // 空ノードの高さは-1、葉ノードの高さは0 + return node == nullptr ? -1 : node->height; + } + + /* 平衡因子を取得 */ + int balanceFactor(TreeNode *node) { + // 空ノードの平衡因子は0 + if (node == nullptr) + return 0; + // ノードの平衡因子 = 左部分木の高さ - 右部分木の高さ + return height(node->left) - height(node->right); + } + + /* ノードを挿入 */ + void insert(int val) { + root = insertHelper(root, val); + } + + /* ノードを削除 */ + void remove(int val) { + root = removeHelper(root, val); + } + + /* ノードを検索 */ + TreeNode *search(int val) { + TreeNode *cur = root; + // ループで検索、葉ノードを通り過ぎたら終了 + while (cur != nullptr) { + // 目標ノードはcurの右部分木にある + if (cur->val < val) + cur = cur->right; + // 目標ノードはcurの左部分木にある + else if (cur->val > val) + cur = cur->left; + // 目標ノードを見つけた、ループを抜ける + else + break; + } + // 目標ノードを返す + return cur; + } + + /*コンストラクタ*/ + AVLTree() : root(nullptr) { + } + + /*デストラクタ*/ + ~AVLTree() { + freeMemoryTree(root); + } +}; + +void testInsert(AVLTree &tree, int val) { + tree.insert(val); + cout << "\nノード " << val << " を挿入後、AVL木は" << endl; + printTree(tree.root); +} + +void testRemove(AVLTree &tree, int val) { + tree.remove(val); + cout << "\nノード " << val << " を削除後、AVL木は" << endl; + printTree(tree.root); +} + +/* ドライバーコード */ +int main() { + /* 空のAVL木を初期化 */ + 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); + cout << "\n見つかったノードオブジェクトは " << node << "、ノード値 =" << node->val << endl; +} \ No newline at end of file diff --git a/ja/codes/cpp/chapter_tree/binary_search_tree.cpp b/ja/codes/cpp/chapter_tree/binary_search_tree.cpp new file mode 100644 index 000000000..63e9a6750 --- /dev/null +++ b/ja/codes/cpp/chapter_tree/binary_search_tree.cpp @@ -0,0 +1,170 @@ +/** + * File: binary_search_tree.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* 二分探索木 */ +class BinarySearchTree { + private: + TreeNode *root; + + public: + /* コンストラクタ */ + BinarySearchTree() { + // 空の木を初期化 + root = nullptr; + } + + /* デストラクタ */ + ~BinarySearchTree() { + freeMemoryTree(root); + } + + /* 二分木のルートノードを取得 */ + TreeNode *getRoot() { + return root; + } + + /* ノードを検索 */ + TreeNode *search(int num) { + TreeNode *cur = root; + // ループで検索、葉ノードを通り過ぎたら終了 + while (cur != nullptr) { + // 目標ノードは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 == nullptr) { + root = new TreeNode(num); + return; + } + TreeNode *cur = root, *pre = nullptr; + // ループで検索、葉ノードを通り過ぎたら終了 + while (cur != nullptr) { + // 重複ノードを見つけた場合、戻る + if (cur->val == num) + return; + pre = cur; + // 挿入位置はcurの右部分木にある + if (cur->val < num) + cur = cur->right; + // 挿入位置はcurの左部分木にある + else + cur = cur->left; + } + // ノードを挿入 + TreeNode *node = new TreeNode(num); + if (pre->val < num) + pre->right = node; + else + pre->left = node; + } + + /* ノードを削除 */ + void remove(int num) { + // 木が空の場合、戻る + if (root == nullptr) + return; + TreeNode *cur = root, *pre = nullptr; + // ループで検索、葉ノードを通り過ぎたら終了 + while (cur != nullptr) { + // 削除するノードを見つけた、ループを抜ける + if (cur->val == num) + break; + pre = cur; + // 削除するノードはcurの右部分木にある + if (cur->val < num) + cur = cur->right; + // 削除するノードはcurの左部分木にある + else + cur = cur->left; + } + // 削除するノードがない場合、戻る + if (cur == nullptr) + return; + // 子ノード数 = 0 または 1 + if (cur->left == nullptr || cur->right == nullptr) { + // 子ノード数 = 0 / 1の場合、child = nullptr / その子ノード + TreeNode *child = cur->left != nullptr ? cur->left : cur->right; + // ノードcurを削除 + if (cur != root) { + if (pre->left == cur) + pre->left = child; + else + pre->right = child; + } else { + // 削除されるノードがルートの場合、ルートを再割り当て + root = child; + } + // メモリを解放 + delete cur; + } + // 子ノード数 = 2 + else { + // curの中順走査の次のノードを取得 + TreeNode *tmp = cur->right; + while (tmp->left != nullptr) { + tmp = tmp->left; + } + int tmpVal = tmp->val; + // ノードtmpを再帰的に削除 + remove(tmp->val); + // curをtmpで置き換え + cur->val = tmpVal; + } + } +}; + +/* ドライバーコード */ +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; + printTree(bst->getRoot()); + + /* ノードを検索 */ + TreeNode *node = bst->search(7); + cout << endl << "見つかったノードオブジェクトは " << node << "、ノード値 =" << node->val << endl; + + /* ノードを挿入 */ + bst->insert(16); + cout << endl << "ノード 16 を挿入後、二分木は\n" << endl; + printTree(bst->getRoot()); + + /* ノードを削除 */ + bst->remove(1); + cout << endl << "ノード 1 を削除後、二分木は\n" << endl; + printTree(bst->getRoot()); + bst->remove(2); + cout << endl << "ノード 2 を削除後、二分木は\n" << endl; + printTree(bst->getRoot()); + bst->remove(4); + 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 new file mode 100644 index 000000000..cfdbea3de --- /dev/null +++ b/ja/codes/cpp/chapter_tree/binary_tree.cpp @@ -0,0 +1,43 @@ +/** + * File: binary_tree.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* ドライバーコード */ +int main() { + /* 二分木を初期化 */ + // ノードを初期化 + 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; + n2->right = n5; + cout << endl << "二分木を初期化\n" << endl; + printTree(n1); + + /* ノードの挿入と削除 */ + TreeNode *P = new TreeNode(0); + // n1 -> n2の間にノードPを挿入 + n1->left = P; + P->left = n2; + cout << endl << "ノード P を挿入後\n" << endl; + printTree(n1); + // ノードPを削除 + n1->left = n2; + 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 new file mode 100644 index 000000000..b9794aa64 --- /dev/null +++ b/ja/codes/cpp/chapter_tree/binary_tree_bfs.cpp @@ -0,0 +1,42 @@ +/** + * File: binary_tree_bfs.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#include "../utils/common.hpp" + +/* レベル順走査 */ +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); // ノード値を保存 + if (node->left != nullptr) + queue.push(node->left); // 左の子ノードをエンキュー + if (node->right != nullptr) + queue.push(node->right); // 右の子ノードをエンキュー + } + return vec; +} + +/* ドライバーコード */ +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 << "レベル順走査のノード順序 = "; + 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 new file mode 100644 index 000000000..3a2f5efc7 --- /dev/null +++ b/ja/codes/cpp/chapter_tree/binary_tree_dfs.cpp @@ -0,0 +1,69 @@ +/** + * File: binary_tree_dfs.cpp + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +#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); +} + +/* 中順走査 */ +void inOrder(TreeNode *root) { + if (root == nullptr) + return; + // 訪問優先度:左部分木 -> ルートノード -> 右部分木 + inOrder(root->left); + vec.push_back(root->val); + inOrder(root->right); +} + +/* 後順走査 */ +void postOrder(TreeNode *root) { + if (root == nullptr) + return; + // 訪問優先度:左部分木 -> 右部分木 -> ルートノード + postOrder(root->left); + postOrder(root->right); + vec.push_back(root->val); +} + +/* ドライバーコード */ +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 << "前順走査のノード順序 = "; + printVector(vec); + + /* 中順走査 */ + vec.clear(); + inOrder(root); + cout << endl << "中順走査のノード順序 = "; + printVector(vec); + + /* 後順走査 */ + vec.clear(); + postOrder(root); + cout << endl << "後順走査のノード順序 = "; + printVector(vec); + + return 0; +} \ No newline at end of file diff --git a/ja/codes/cpp/utils/common.hpp b/ja/codes/cpp/utils/common.hpp new file mode 100644 index 000000000..e71d26180 --- /dev/null +++ b/ja/codes/cpp/utils/common.hpp @@ -0,0 +1,28 @@ +/** + * File: common.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "list_node.hpp" +#include "print_utils.hpp" +#include "tree_node.hpp" +#include "vertex.hpp" + +using namespace std; \ No newline at end of file diff --git a/ja/codes/cpp/utils/list_node.hpp b/ja/codes/cpp/utils/list_node.hpp new file mode 100644 index 000000000..a75d6f4a0 --- /dev/null +++ b/ja/codes/cpp/utils/list_node.hpp @@ -0,0 +1,42 @@ +/** + * File: list_node.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include + +using namespace std; + +/* 連結リストノード */ +struct ListNode { + int val; + ListNode *next; + ListNode(int x) : val(x), next(nullptr) { + } +}; + +/* 配列を連結リストに逆シリアル化する */ +ListNode *vecToLinkedList(vector list) { + ListNode *dum = new ListNode(0); + ListNode *head = dum; + for (int val : list) { + head->next = new ListNode(val); + head = head->next; + } + 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 new file mode 100644 index 000000000..3fac372fc --- /dev/null +++ b/ja/codes/cpp/utils/print_utils.hpp @@ -0,0 +1,228 @@ +/** + * File: print_utils.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com), msk397 (machangxinq@gmail.com), LoneRanger(836253168@qq.com) + */ + +#pragma once + +#include "list_node.hpp" +#include "tree_node.hpp" +#include +#include +#include +#include + +/* ベクター内の要素を検索する */ +template int vecFind(const vector &vec, T ele) { + int j = INT_MAX; + for (int i = 0; i < vec.size(); i++) { + if (vec[i] == ele) { + j = i; + } + } + return j; +} + +/* ベクターを区切り文字で連結する */ +template string strJoin(const string &delim, const T &vec) { + ostringstream s; + for (const auto &i : vec) { + if (&i != &vec[0]) { + s << delim; + } + s << i; + } + return s.str(); +} + +/* 文字列をn回繰り返す */ +string strRepeat(string str, int n) { + ostringstream os; + for (int i = 0; i < n; i++) + os << str; + return os.str(); +} + +/* 配列を印刷する */ +template void printArray(T *arr, int n) { + cout << "["; + for (int i = 0; i < n - 1; i++) { + cout << arr[i] << ", "; + } + if (n >= 1) + cout << arr[n - 1] << "]" << endl; + else + cout << "]" << endl; +} + +/* ベクター文字列オブジェクトを取得する */ +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) + cout << " " + getVectorString(list) + "," << '\n'; + cout << "]" << '\n'; +} + +/* 連結リストを印刷する */ +void printLinkedList(ListNode *head) { + vector list; + while (head != nullptr) { + list.push_back(head->val); + head = head->next; + } + + cout << strJoin(" -> ", list) << '\n'; +} + +struct Trunk { + Trunk *prev; + string str; + Trunk(Trunk *prev, string str) { + this->prev = prev; + this->str = str; + } +}; + +void showTrunks(Trunk *p) { + if (p == nullptr) { + return; + } + + showTrunks(p->prev); + cout << p->str; +} + +/** + * 二分木を印刷する + * この木プリンターはTECHIE DELIGHTから借用しました + * https://www.techiedelight.com/c-program-print-binary-tree/ + */ +void printTree(TreeNode *root, Trunk *prev, bool isRight) { + if (root == nullptr) { + return; + } + + string prev_str = " "; + Trunk 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); + cout << " " << root->val << endl; + + if (prev) { + prev->str = prev_str; + } + trunk.str = " |"; + + printTree(root->left, &trunk, false); +} + +/* 二分木を印刷する */ +void printTree(TreeNode *root) { + printTree(root, nullptr, false); +} + +/* スタックを印刷する */ +template void printStack(stack stk) { + // 入力スタックを逆順にする + stack tmp; + while (!stk.empty()) { + tmp.push(stk.top()); + stk.pop(); + } + // 印刷する文字列を生成 + ostringstream s; + bool flag = true; + while (!tmp.empty()) { + if (flag) { + s << tmp.top(); + flag = false; + } else + s << ", " << tmp.top(); + tmp.pop(); + } + cout << "[" + s.str() + "]" << '\n'; +} + +/* キューを印刷する */ +template void printQueue(queue queue) { + // 印刷する文字列を生成 + ostringstream s; + bool flag = true; + while (!queue.empty()) { + if (flag) { + s << queue.front(); + flag = false; + } else + s << ", " << queue.front(); + queue.pop(); + } + cout << "[" + s.str() + "]" << '\n'; +} + +/* デックを印刷する */ +template void printDeque(deque deque) { + // 印刷する文字列を生成 + ostringstream s; + bool flag = true; + while (!deque.empty()) { + if (flag) { + s << deque.front(); + flag = false; + } else + s << ", " << deque.front(); + deque.pop_front(); + } + cout << "[" + s.str() + "]" << '\n'; +} + +/* ハッシュテーブルを印刷する */ +// キー値ペアの型を指定するためにテンプレートパラメータTKeyとTValueを定義 +template void printHashMap(unordered_map map) { + for (auto kv : map) { + cout << kv.first << " -> " << kv.second << '\n'; + } +} + +/* priority_queueコンテナの基礎となるストレージを公開する */ +template S &Container(priority_queue &pq) { + struct HackedQueue : private priority_queue { + static S &Container(priority_queue &pq) { + return pq.*&HackedQueue::c; + } + }; + return HackedQueue::Container(pq); +} + +/* ヒープ(優先度付きキュー)を印刷する */ +template void printHeap(priority_queue &heap) { + vector vec = Container(heap); + cout << "ヒープの配列表現:"; + printVector(vec); + 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 new file mode 100644 index 000000000..425920cc4 --- /dev/null +++ b/ja/codes/cpp/utils/tree_node.hpp @@ -0,0 +1,84 @@ +/** + * File: tree_node.hpp + * Created Time: 2021-12-19 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include +#include + +using namespace std; + +/* 二分木ノード構造 */ +struct TreeNode { + int val{}; + int height = 0; + TreeNode *parent{}; + TreeNode *left{}; + TreeNode *right{}; + TreeNode() = default; + explicit TreeNode(int x, TreeNode *parent = nullptr) : val(x), parent(parent) { + } +}; + +// シリアル化エンコーディング規則については以下を参照: +// 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 *vectorToTreeDFS(vector &arr, int i) { + if (i < 0 || i >= arr.size() || arr[i] == INT_MAX) { + return nullptr; + } + TreeNode *root = new TreeNode(arr[i]); + root->left = vectorToTreeDFS(arr, 2 * i + 1); + root->right = vectorToTreeDFS(arr, 2 * i + 2); + return root; +} + +/* 配列を二分木に逆シリアル化する */ +TreeNode *vectorToTree(vector arr) { + return vectorToTreeDFS(arr, 0); +} + +/* 二分木を配列にシリアル化する:再帰的 */ +void treeToVecorDFS(TreeNode *root, int i, vector &res) { + if (root == nullptr) + return; + while (i >= res.size()) { + res.push_back(INT_MAX); + } + res[i] = root->val; + treeToVecorDFS(root->left, 2 * i + 1, 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 new file mode 100644 index 000000000..34b1d519a --- /dev/null +++ b/ja/codes/cpp/utils/vertex.hpp @@ -0,0 +1,36 @@ +/** + * File: vertex.hpp + * Created Time: 2023-03-02 + * Author: krahets (krahets@163.com) + */ + +#pragma once + +#include + +using namespace std; + +/* 頂点クラス */ +struct Vertex { + int val; + Vertex(int x) : val(x) { + } +}; + +/* 値のリストvals を入力し、頂点のリストvets を返す */ +vector valsToVets(vector vals) { + vector vets; + for (int val : vals) { + vets.push_back(new Vertex(val)); + } + return vets; +} + +/* 頂点のリスト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/java/chapter_array_and_linkedlist/array.java b/ja/codes/java/chapter_array_and_linkedlist/array.java new file mode 100644 index 000000000..19f1d0780 --- /dev/null +++ b/ja/codes/java/chapter_array_and_linkedlist/array.java @@ -0,0 +1,105 @@ +/** + * File: array.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_array_and_linkedlist; + +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; + +public class array { + /* 要素へのランダムアクセス */ + static int randomAccess(int[] nums) { + // 区間 [0, nums.length) からランダムに数を選択 + 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]; + } + // 拡張後の新しい配列を返す + return res; + } + + /* `index` に要素 num を挿入 */ + static 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` の要素を削除 */ + static void remove(int[] nums, int index) { + // `index` より後のすべての要素を1つ前に移動 + for (int i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } + } + + /* 配列を走査 */ + 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) + return i; + } + return -1; + } + + /* ドライバーコード */ + public static void main(String[] args) { + /* 配列を初期化 */ + int[] arr = new int[5]; + System.out.println("配列 arr = " + Arrays.toString(arr)); + int[] nums = { 1, 3, 2, 5, 4 }; + System.out.println("配列 nums = " + Arrays.toString(nums)); + + /* ランダムアクセス */ + int randomNum = randomAccess(nums); + System.out.println("nums からランダム要素を取得 = " + randomNum); + + /* 長さの拡張 */ + nums = extend(nums, 3); + System.out.println("配列の長さを8に拡張し、nums = " + Arrays.toString(nums)); + + /* 要素の挿入 */ + insert(nums, 6, 3); + System.out.println("インデックス3に数値6を挿入し、nums = " + Arrays.toString(nums)); + + /* 要素の削除 */ + remove(nums, 2); + System.out.println("インデックス2の要素を削除し、nums = " + Arrays.toString(nums)); + + /* 配列の走査 */ + traverse(nums); + + /* 要素の検索 */ + int index = find(nums, 3); + 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 new file mode 100644 index 000000000..3ec773dec --- /dev/null +++ b/ja/codes/java/chapter_array_and_linkedlist/linked_list.java @@ -0,0 +1,86 @@ +/** + * File: linked_list.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_array_and_linkedlist; + +import utils.*; + +public class linked_list { + /* 連結リストでノード n0 の後にノード P を挿入 */ + static void insert(ListNode n0, ListNode P) { + ListNode n1 = n0.next; + P.next = n1; + n0.next = P; + } + + /* 連結リストでノード n0 の後の最初のノードを削除 */ + static void remove(ListNode n0) { + if (n0.next == null) + return; + // n0 -> P -> n1 + ListNode P = n0.next; + ListNode n1 = P.next; + n0.next = n1; + } + + /* 連結リストの `index` のノードにアクセス */ + static ListNode access(ListNode head, int index) { + for (int i = 0; i < index; i++) { + if (head == null) + return null; + head = head.next; + } + return head; + } + + /* 連結リストで値 target を持つ最初のノードを検索 */ + static int find(ListNode head, int target) { + int index = 0; + while (head != null) { + if (head.val == target) + return index; + head = head.next; + index++; + } + return -1; + } + + /* ドライバーコード */ + 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("初期化された連結リストは"); + 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); + + /* ノードの検索 */ + int index = find(n0, 2); + 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 new file mode 100644 index 000000000..e5459a4bc --- /dev/null +++ b/ja/codes/java/chapter_array_and_linkedlist/list.java @@ -0,0 +1,66 @@ +/** + * File: list.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_array_and_linkedlist; + +import java.util.*; + +public class list { + public static void main(String[] args) { + /* リストの初期化 */ + // 配列の要素型は Integer[]、int のラッパークラス + 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); + + /* 要素の更新 */ + nums.set(1, 0); + System.out.println("インデックス1の要素を0に更新し、nums = " + nums); + + /* リストのクリア */ + nums.clear(); + System.out.println("リストをクリアした後、nums = " + nums); + + /* 末尾に要素を追加 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + System.out.println("要素を追加した後、nums = " + nums); + + /* 中間に要素を挿入 */ + nums.add(3, 6); + System.out.println("インデックス3に数値6を挿入し、nums = " + nums); + + /* 要素の削除 */ + nums.remove(3); + 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つのリストの連結 */ + List nums1 = new ArrayList<>(Arrays.asList(new Integer[] { 6, 8, 7, 10, 9 })); + nums.addAll(nums1); + System.out.println("リスト nums1 を nums に連結し、nums = " + nums); + + /* リストのソート */ + Collections.sort(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 new file mode 100644 index 000000000..2d0a100e5 --- /dev/null +++ b/ja/codes/java/chapter_array_and_linkedlist/my_list.java @@ -0,0 +1,147 @@ +/** + * File: my_list.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_array_and_linkedlist; + +import java.util.*; + +/* リストクラス */ +class MyList { + private int[] arr; // 配列(リスト要素を格納) + private int capacity = 10; // リスト容量 + 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("インデックスが範囲外です"); + arr[index] = num; + } + + /* 末尾に要素を追加 */ + public void add(int num) { + // 要素数が容量を超える場合、拡張メカニズムを実行 + if (size == capacity()) + extendCapacity(); + arr[size] = num; + // 要素数を更新 + size++; + } + + /* 中間に要素を挿入 */ + public void insert(int index, int num) { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("インデックスが範囲外です"); + // 要素数が容量を超える場合、拡張メカニズムを実行 + if (size == capacity()) + extendCapacity(); + // `index` より後のすべての要素を1つ後ろに移動 + for (int j = size - 1; j >= index; j--) { + arr[j + 1] = arr[j]; + } + arr[index] = num; + // 要素数を更新 + size++; + } + + /* 要素の削除 */ + public int remove(int index) { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("インデックスが範囲外です"); + int num = arr[index]; + // `index` より後のすべての要素を1つ前に移動 + for (int j = index; j < size - 1; j++) { + arr[j] = arr[j + 1]; + } + // 要素数を更新 + size--; + // 削除された要素を返す + return num; + } + + /* リストを拡張 */ + public void extendCapacity() { + // 元の配列の長さを 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); + } + return arr; + } +} + +public class my_list { + /* ドライバーコード */ + public static void main(String[] args) { + /* リストの初期化 */ + MyList nums = new MyList(); + /* 末尾に要素を追加 */ + nums.add(1); + nums.add(3); + nums.add(2); + nums.add(5); + nums.add(4); + System.out.println("リスト nums = " + Arrays.toString(nums.toArray()) + + ", 容量 = " + nums.capacity() + ", 長さ = " + nums.size()); + + /* 中間に要素を挿入 */ + nums.insert(3, 6); + System.out.println("インデックス3に数値6を挿入し、nums = " + Arrays.toString(nums.toArray())); + + /* 要素の削除 */ + nums.remove(3); + System.out.println("インデックス3の要素を削除し、nums = " + Arrays.toString(nums.toArray())); + + /* 要素へのアクセス */ + int num = nums.get(1); + System.out.println("インデックス1の要素にアクセス、取得した num = " + num); + + /* 要素の更新 */ + nums.set(1, 0); + System.out.println("インデックス1の要素を0に更新し、nums = " + Arrays.toString(nums.toArray())); + + /* 拡張メカニズムのテスト */ + for (int i = 0; i < 10; i++) { + // i = 5 の時、リスト長がリスト容量を超え、この時点で拡張メカニズムが実行される + nums.add(i); + } + 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 new file mode 100644 index 000000000..1f793a4f2 --- /dev/null +++ b/ja/codes/java/chapter_backtracking/n_queens.java @@ -0,0 +1,77 @@ +/** + * File: n_queens.java + * Created Time: 2023-05-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class n_queens { + /* バックトラッキングアルゴリズム: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) { + copyState.add(new ArrayList<>(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.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 クイーンを解く */ + public static List>> nQueens(int n) { + // n*n サイズのチェスボードを初期化、'Q' はクイーンを表し、'#' は空のスポットを表す + List> state = new ArrayList<>(); + for (int i = 0; i < n; i++) { + List row = new ArrayList<>(); + for (int j = 0; j < n; j++) { + row.add("#"); + } + state.add(row); + } + 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); + + return res; + } + + public static void main(String[] args) { + int n = 4; + List>> res = nQueens(n); + + System.out.println("チェスボードの次元を " + n + " として入力"); + System.out.println("クイーン配置解の総数 = " + res.size()); + for (List> state : res) { + System.out.println("--------------------"); + for (List row : state) { + System.out.println(row); + } + } + } +} \ 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 new file mode 100644 index 000000000..3cb122cb8 --- /dev/null +++ b/ja/codes/java/chapter_backtracking/permutations_i.java @@ -0,0 +1,51 @@ +/** + * File: permutations_i.java + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class permutations_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; + } + // すべての選択肢を走査 + 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 */ + static List> permutationsI(int[] nums) { + List> res = new ArrayList>(); + backtrack(new ArrayList(), nums, new boolean[nums.length], res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 1, 2, 3 }; + + List> res = permutationsI(nums); + + 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 new file mode 100644 index 000000000..c36e0f33e --- /dev/null +++ b/ja/codes/java/chapter_backtracking/permutations_ii.java @@ -0,0 +1,53 @@ +/** + * File: permutations_ii.java + * Created Time: 2023-04-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class permutations_ii { + /* バックトラッキングアルゴリズム:順列 II */ + static void backtrack(List state, int[] choices, boolean[] selected, List> res) { + // 状態の長さが要素数と等しくなったら、解を記録 + if (state.size() == choices.length) { + res.add(new ArrayList(state)); + return; + } + // すべての選択肢を走査 + 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); // 選択された要素値を記録 + selected[i] = true; + state.add(choice); + // 次のラウンドの選択に進む + backtrack(state, choices, selected, res); + // 回退:選択を取り消し、前の状態に復元 + selected[i] = false; + state.remove(state.size() - 1); + } + } + } + + /* 順列 II */ + static List> permutationsII(int[] nums) { + List> res = new ArrayList>(); + backtrack(new ArrayList(), nums, new boolean[nums.length], res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 1, 2, 2 }; + + List> res = permutationsII(nums); + + 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 new file mode 100644 index 000000000..e85e66951 --- /dev/null +++ b/ja/codes/java/chapter_backtracking/preorder_traversal_i_compact.java @@ -0,0 +1,44 @@ +/** + * File: preorder_traversal_i_compact.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_i_compact { + static List res; + + /* 前順走査:例1 */ + static void preOrder(TreeNode root) { + if (root == null) { + return; + } + if (root.val == 7) { + // 解を記録 + res.add(root); + } + preOrder(root.left); + preOrder(root.right); + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\n二分木を初期化"); + PrintUtil.printTree(root); + + // 前順走査 + res = new ArrayList<>(); + preOrder(root); + + 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 new file mode 100644 index 000000000..b65dc42fb --- /dev/null +++ b/ja/codes/java/chapter_backtracking/preorder_traversal_ii_compact.java @@ -0,0 +1,52 @@ +/** + * File: preorder_traversal_ii_compact.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_ii_compact { + static List path; + static List> res; + + /* 前順走査:例2 */ + static void preOrder(TreeNode root) { + if (root == null) { + return; + } + // 試行 + path.add(root); + if (root.val == 7) { + // 解を記録 + res.add(new ArrayList<>(path)); + } + preOrder(root.left); + preOrder(root.right); + // 回退 + path.remove(path.size() - 1); + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\n二分木を初期化"); + PrintUtil.printTree(root); + + // 前順走査 + path = new ArrayList<>(); + res = new ArrayList<>(); + preOrder(root); + + System.out.println("\nルートからノード7までのすべてのパスを出力"); + for (List path : res) { + List vals = new ArrayList<>(); + for (TreeNode node : path) { + vals.add(node.val); + } + 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 new file mode 100644 index 000000000..ad60d7a70 --- /dev/null +++ b/ja/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java @@ -0,0 +1,53 @@ +/** + * File: preorder_traversal_iii_compact.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_iii_compact { + static List path; + static List> res; + + /* 前順走査:例3 */ + static void preOrder(TreeNode root) { + // 剪定 + if (root == null || root.val == 3) { + return; + } + // 試行 + path.add(root); + if (root.val == 7) { + // 解を記録 + res.add(new ArrayList<>(path)); + } + preOrder(root.left); + preOrder(root.right); + // 回退 + path.remove(path.size() - 1); + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\n二分木を初期化"); + PrintUtil.printTree(root); + + // 前順走査 + path = new ArrayList<>(); + res = new ArrayList<>(); + preOrder(root); + + System.out.println("\nルートからノード7までのすべてのパスを出力、値3のノードは含まない"); + for (List path : res) { + List vals = new ArrayList<>(); + for (TreeNode node : path) { + vals.add(node.val); + } + 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 new file mode 100644 index 000000000..b0e396550 --- /dev/null +++ b/ja/codes/java/chapter_backtracking/preorder_traversal_iii_template.java @@ -0,0 +1,77 @@ +/** + * File: preorder_traversal_iii_template.java + * Created Time: 2023-04-16 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import utils.*; +import java.util.*; + +public class preorder_traversal_iii_template { + /* 現在の状態が解かどうかを判定 */ + static boolean isSolution(List state) { + return !state.isEmpty() && state.get(state.size() - 1).val == 7; + } + + /* 解を記録 */ + static void recordSolution(List state, List> res) { + res.add(new ArrayList<>(state)); + } + + /* 現在の状態下で選択が合法かどうかを判定 */ + static boolean isValid(List state, TreeNode choice) { + return choice != null && choice.val != 3; + } + + /* 状態を更新 */ + static void makeChoice(List state, TreeNode choice) { + state.add(choice); + } + + /* 状態を復元 */ + static void undoChoice(List state, TreeNode choice) { + state.remove(state.size() - 1); + } + + /* バックトラッキングアルゴリズム:例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); + } + } + } + + public static void main(String[] args) { + TreeNode root = TreeNode.listToTree(Arrays.asList(1, 7, 3, 4, 5, 6, 7)); + System.out.println("\n二分木を初期化"); + PrintUtil.printTree(root); + + // バックトラッキングアルゴリズム + List> res = new ArrayList<>(); + backtrack(new ArrayList<>(), Arrays.asList(root), res); + + System.out.println("\nルートからノード7までのすべてのパスを出力、パスには値3のノードを含まないことが要求される"); + for (List path : res) { + List vals = new ArrayList<>(); + for (TreeNode node : path) { + vals.add(node.val); + } + 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 new file mode 100644 index 000000000..fe863417c --- /dev/null +++ b/ja/codes/java/chapter_backtracking/subset_sum_i.java @@ -0,0 +1,55 @@ +/** + * File: subset_sum_i.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class subset_sum_i { + /* バックトラッキングアルゴリズム:部分集合和 I */ + static void backtrack(List state, int target, int[] choices, int start, List> res) { + // 部分集合の和がtargetと等しいとき、解を記録 + if (target == 0) { + res.add(new ArrayList<>(state)); + return; + } + // すべての選択肢を走査 + // 剪定二:startから走査を開始し、重複する部分集合の生成を回避 + for (int i = start; i < choices.length; i++) { + // 剪定一:部分集合の和がtargetを超えた場合、即座にループを終了 + // 配列がソートされているため、後の要素はさらに大きく、部分集合の和は必ずtargetを超える + if (target - choices[i] < 0) { + break; + } + // 試行:選択を行い、target、startを更新 + state.add(choices[i]); + // 次のラウンドの選択に進む + backtrack(state, target - choices[i], choices, i, res); + // 回退:選択を取り消し、前の状態に復元 + state.remove(state.size() - 1); + } + } + + /* 部分集合和 I を解く */ + static List> subsetSumI(int[] nums, int target) { + List state = new ArrayList<>(); // 状態(部分集合) + Arrays.sort(nums); // nums をソート + int start = 0; // 走査の開始点 + List> res = new ArrayList<>(); // 結果リスト(部分集合リスト) + backtrack(state, target, nums, start, res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 3, 4, 5 }; + int target = 9; + + List> res = subsetSumI(nums, target); + + System.out.println("入力配列 nums = " + Arrays.toString(nums) + ", target = " + target); + 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 new file mode 100644 index 000000000..19cd8d2dd --- /dev/null +++ b/ja/codes/java/chapter_backtracking/subset_sum_i_naive.java @@ -0,0 +1,53 @@ +/** + * File: subset_sum_i_naive.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class subset_sum_i_naive { + /* バックトラッキングアルゴリズム:部分集合和 I */ + static void backtrack(List state, int target, int total, int[] choices, List> res) { + // 部分集合の和がtargetと等しいとき、解を記録 + if (total == target) { + res.add(new ArrayList<>(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.remove(state.size() - 1); + } + } + + /* 部分集合和 I を解く(重複する部分集合を含む) */ + static List> subsetSumINaive(int[] nums, int target) { + List state = new ArrayList<>(); // 状態(部分集合) + int total = 0; // 部分集合の和 + List> res = new ArrayList<>(); // 結果リスト(部分集合リスト) + backtrack(state, target, total, nums, res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 3, 4, 5 }; + int target = 9; + + List> res = subsetSumINaive(nums, target); + + System.out.println("入力配列 nums = " + Arrays.toString(nums) + ", target = " + target); + 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 new file mode 100644 index 000000000..dbfbde052 --- /dev/null +++ b/ja/codes/java/chapter_backtracking/subset_sum_ii.java @@ -0,0 +1,60 @@ +/** + * File: subset_sum_ii.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_backtracking; + +import java.util.*; + +public class subset_sum_ii { + /* バックトラッキングアルゴリズム:部分集合和 II */ + static void backtrack(List state, int target, int[] choices, int start, List> res) { + // 部分集合の和がtargetと等しいとき、解を記録 + if (target == 0) { + res.add(new ArrayList<>(state)); + return; + } + // すべての選択肢を走査 + // 剪定二:startから走査を開始し、重複する部分集合の生成を回避 + // 剪定三:startから走査を開始し、同じ要素の繰り返し選択を回避 + for (int i = start; i < choices.length; i++) { + // 剪定一:部分集合の和がtargetを超えた場合、即座にループを終了 + // 配列がソートされているため、後の要素はさらに大きく、部分集合の和は必ずtargetを超える + if (target - choices[i] < 0) { + break; + } + // 剪定四:要素が左の要素と等しい場合、検索ブランチの重複を示すのでスキップ + 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.remove(state.size() - 1); + } + } + + /* 部分集合和 II を解く */ + static List> subsetSumII(int[] nums, int target) { + List state = new ArrayList<>(); // 状態(部分集合) + Arrays.sort(nums); // nums をソート + int start = 0; // 走査の開始点 + List> res = new ArrayList<>(); // 結果リスト(部分集合リスト) + backtrack(state, target, nums, start, res); + return res; + } + + public static void main(String[] args) { + int[] nums = { 4, 4, 5 }; + int target = 9; + + List> res = subsetSumII(nums, target); + + System.out.println("入力配列 nums = " + Arrays.toString(nums) + ", target = " + target); + 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 new file mode 100644 index 000000000..bc5e3fc83 --- /dev/null +++ b/ja/codes/java/chapter_computational_complexity/iteration.java @@ -0,0 +1,76 @@ +/** + * File: iteration.java + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +public class iteration { + /* for ループ */ + static int forLoop(int n) { + int res = 0; + // 1, 2, ..., n-1, n の合計をループ計算 + for (int i = 1; i <= n; i++) { + res += i; + } + return res; + } + + /* while ループ */ + static 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つの更新) */ + static int whileLoopII(int n) { + int res = 0; + int i = 1; // 条件変数を初期化 + // 1, 4, 10, ... の合計をループ計算 + while (i <= n) { + res += i; + // 条件変数を更新 + i++; + i *= 2; + } + return res; + } + + /* 2重 for ループ */ + static String nestedForLoop(int n) { + StringBuilder res = new StringBuilder(); + // ループ 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(); + } + + /* ドライバーコード */ + public static void main(String[] args) { + int n = 5; + int res; + + res = forLoop(n); + System.out.println("\nfor ループの合計結果 res = " + res); + + res = whileLoop(n); + System.out.println("\nwhile ループの合計結果 res = " + res); + + res = whileLoopII(n); + System.out.println("\nwhile ループ(2つの更新)の合計結果 res = " + res); + + String resStr = nestedForLoop(n); + System.out.println("\n2重 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 new file mode 100644 index 000000000..b1d79aa86 --- /dev/null +++ b/ja/codes/java/chapter_computational_complexity/recursion.java @@ -0,0 +1,79 @@ +/** + * File: recursion.java + * Created Time: 2023-08-24 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +import java.util.Stack; + +public class recursion { + /* 再帰 */ + static int recur(int n) { + // 終了条件 + if (n == 1) + 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 + return res; + } + + /* 末尾再帰 */ + static int tailRecur(int n, int res) { + // 終了条件 + if (n == 0) + return res; + // 末尾再帰呼び出し + return tailRecur(n - 1, res + n); + } + + /* フィボナッチ数列:再帰 */ + static 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; + } + + /* ドライバーコード */ + public static void main(String[] args) { + int n = 5; + int res; + + res = recur(n); + System.out.println("\n再帰関数の合計結果 res = " + res); + + res = forLoopRecur(n); + 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); + } +} \ 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 new file mode 100644 index 000000000..6db3f2613 --- /dev/null +++ b/ja/codes/java/chapter_computational_complexity/space_complexity.java @@ -0,0 +1,110 @@ +/** + * File: space_complexity.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +import utils.*; +import java.util.*; + +public class space_complexity { + /* 関数 */ + static int function() { + // 何らかの操作を実行 + return 0; + } + + /* 定数計算量 */ + static void constant(int n) { + // 定数、変数、オブジェクトは O(1) 空間を占める + final int a = 0; + int b = 0; + int[] nums = new int[10000]; + ListNode node = new ListNode(0); + // ループ内の変数は O(1) 空間を占める + for (int i = 0; i < n; i++) { + int c = 0; + } + // ループ内の関数は O(1) 空間を占める + for (int i = 0; i < n; i++) { + function(); + } + } + + /* 線形計算量 */ + static void linear(int n) { + // 長さ n の配列は O(n) 空間を占める + int[] nums = new int[n]; + // 長さ n のリストは O(n) 空間を占める + List nodes = new ArrayList<>(); + for (int i = 0; i < n; i++) { + nodes.add(new ListNode(i)); + } + // 長さ 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) + return; + linearRecur(n - 1); + } + + /* 二次計算量 */ + static void quadratic(int n) { + // 行列は O(n^2) 空間を占める + int[][] numMatrix = new int[n][n]; + // 二次元リストは O(n^2) 空間を占める + List> numList = new ArrayList<>(); + for (int i = 0; i < n; i++) { + List tmp = new ArrayList<>(); + for (int j = 0; j < n; j++) { + tmp.add(0); + } + numList.add(tmp); + } + } + + /* 二次計算量(再帰実装) */ + static int quadraticRecur(int n) { + if (n <= 0) + return 0; + // 配列 nums の長さ = n, n-1, ..., 2, 1 + int[] nums = new int[n]; + System.out.println("再帰 n = " + n + " の nums の長さ = " + nums.length); + return quadraticRecur(n - 1); + } + + /* 指数計算量(完全二分木の構築) */ + static TreeNode buildTree(int n) { + if (n == 0) + return null; + TreeNode root = new TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; + } + + /* ドライバーコード */ + 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 new file mode 100644 index 000000000..aaac409cb --- /dev/null +++ b/ja/codes/java/chapter_computational_complexity/time_complexity.java @@ -0,0 +1,167 @@ +/** + * File: time_complexity.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +public class time_complexity { + /* 定数計算量 */ + static int constant(int n) { + int count = 0; + int size = 100000; + for (int i = 0; i < size; i++) + count++; + return count; + } + + /* 線形計算量 */ + static int linear(int n) { + int count = 0; + for (int i = 0; i < n; i++) + count++; + 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 の二乗に比例 + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + count++; + } + } + return count; + } + + /* 二次計算量(バブルソート) */ + static 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] をスワップ + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 要素のスワップには3つの個別操作が含まれる + } + } + } + return count; + } + + /* 指数計算量(ループ実装) */ + static int exponential(int n) { + int count = 0, base = 1; + // セルは毎ラウンド2つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成 + for (int i = 0; i < n; i++) { + for (int j = 0; j < base; j++) { + count++; + } + base *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + 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) { + n = n / 2; + count++; + } + 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; + int count = linearLogRecur(n / 2) + linearLogRecur(n / 2); + for (int i = 0; i < n; i++) { + count++; + } + return count; + } + + /* 階乗計算量(再帰実装) */ + static 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; + } + + /* ドライバーコード */ + public static void main(String[] args) { + // n を変更して、さまざまな計算量での操作回数の変化傾向を体験可能 + int n = 8; + System.out.println("入力データサイズ n = " + n); + + int count = constant(n); + System.out.println("定数計算量の操作回数 = " + count); + + count = linear(n); + System.out.println("線形計算量の操作回数 = " + count); + count = arrayTraversal(new int[n]); + System.out.println("線形計算量の操作回数(配列走査) = " + count); + + count = quadratic(n); + System.out.println("二次計算量の操作回数 = " + 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); + + count = exponential(n); + System.out.println("指数計算量の操作回数(ループ実装) = " + count); + count = expRecur(n); + System.out.println("指数計算量の操作回数(再帰実装) = " + count); + + count = logarithmic(n); + System.out.println("対数計算量の操作回数(ループ実装) = " + count); + count = logRecur(n); + System.out.println("対数計算量の操作回数(再帰実装) = " + count); + + count = linearLogRecur(n); + System.out.println("線形対数計算量の操作回数(再帰実装) = " + count); + + count = factorialRecur(n); + 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 new file mode 100644 index 000000000..58d99eb66 --- /dev/null +++ b/ja/codes/java/chapter_computational_complexity/worst_best_time_complexity.java @@ -0,0 +1,50 @@ +/** + * File: worst_best_time_complexity.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_computational_complexity; + +import java.util.*; + +public class worst_best_time_complexity { + /* 要素 {1, 2, ..., n} をランダムにシャッフルした配列を生成 */ + static int[] randomNumbers(int n) { + Integer[] nums = new Integer[n]; + // 配列 nums = { 1, 2, 3, ..., n } を生成 + for (int i = 0; i < n; i++) { + nums[i] = i + 1; + } + // 配列要素をランダムにシャッフル + Collections.shuffle(Arrays.asList(nums)); + // Integer[] -> int[] + int[] res = new int[n]; + for (int i = 0; i < n; i++) { + res[i] = nums[i]; + } + return res; + } + + /* 配列 nums で数値1のインデックスを見つける */ + static 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; + } + + /* ドライバーコード */ + 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); + } + } +} \ 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 new file mode 100644 index 000000000..0ca339e04 --- /dev/null +++ b/ja/codes/java/chapter_divide_and_conquer/binary_search_recur.java @@ -0,0 +1,45 @@ +/** + * File: binary_search_recur.java + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +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 を返す + if (i > j) { + return -1; + } + // 中点インデックス m を計算 + int m = i + (j - i) / 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; + } + } + + /* 二分探索 */ + static int binarySearch(int[] nums, int target) { + int n = nums.length; + // 問題 f(0, n-1) を解く + return dfs(nums, target, 0, n - 1); + } + + public static void main(String[] args) { + 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); + } +} \ 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 new file mode 100644 index 000000000..d6738ddba --- /dev/null +++ b/ja/codes/java/chapter_divide_and_conquer/build_tree.java @@ -0,0 +1,51 @@ +/** + * File: build_tree.java + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_divide_and_conquer; + +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 を問い合わせて左右の部分木を分割 + 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) { + // ハッシュテーブルを初期化し、中間順序の要素からインデックスへのマッピングを格納 + Map inorderMap = new HashMap<>(); + for (int i = 0; i < inorder.length; i++) { + inorderMap.put(inorder[i], i); + } + TreeNode root = dfs(preorder, inorderMap, 0, 0, inorder.length - 1); + return root; + } + + public static void main(String[] args) { + 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)); + + TreeNode root = buildTree(preorder, inorder); + 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 new file mode 100644 index 000000000..a0046e481 --- /dev/null +++ b/ja/codes/java/chapter_divide_and_conquer/hanota.java @@ -0,0 +1,59 @@ +/** + * File: hanota.java + * Created Time: 2023-07-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_divide_and_conquer; + +import java.util.*; + +public class hanota { + /* 円盤を移動 */ + static void move(List src, List tar) { + // src の最上部から円盤を取り出す + Integer pan = src.remove(src.size() - 1); + // 円盤を tar の最上部に配置 + tar.add(pan); + } + + /* ハノイの塔問題 f(i) を解く */ + static void dfs(int i, List src, List buf, List tar) { + // src に円盤が1つだけ残っている場合、それを tar に移動 + if (i == 1) { + move(src, tar); + return; + } + // 部分問題 f(i-1):tar の助けを借りて、上位 i-1 個の円盤を src から buf に移動 + dfs(i - 1, src, tar, buf); + // 部分問題 f(1):残りの1つの円盤を src から tar に移動 + move(src, tar); + // 部分問題 f(i-1):src の助けを借りて、上位 i-1 個の円盤を buf から tar に移動 + dfs(i - 1, buf, src, tar); + } + + /* ハノイの塔問題を解く */ + static void solveHanota(List A, List B, List C) { + int n = A.size(); + // B の助けを借りて、上位 n 個の円盤を A から 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("A = " + A); + System.out.println("B = " + B); + System.out.println("C = " + C); + + solveHanota(A, B, C); + + 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 new file mode 100644 index 000000000..cea633af8 --- /dev/null +++ b/ja/codes/java/chapter_dynamic_programming/climbing_stairs_backtrack.java @@ -0,0 +1,44 @@ +/** + * File: climbing_stairs_backtrack.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.*; + +public class climbing_stairs_backtrack { + /* バックトラッキング */ + public static void backtrack(List choices, int state, int n, List res) { + // n段目に到達したとき、解の数に1を加える + if (state == n) + res.set(0, res.get(0) + 1); + // すべての選択肢を走査 + for (Integer choice : choices) { + // 剪定: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 res = new ArrayList<>(); + res.add(0); // res[0] を使用して解の数を記録 + backtrack(choices, state, n, res); + return res.get(0); + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsBacktrack(n); + 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 new file mode 100644 index 000000000..2f6bc9734 --- /dev/null +++ b/ja/codes/java/chapter_dynamic_programming/climbing_stairs_constraint_dp.java @@ -0,0 +1,36 @@ +/** + * File: climbing_stairs_constraint_dp.java + * Created Time: 2023-07-01 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class climbing_stairs_constraint_dp { + /* 制約付き階段登り:動的プログラミング */ + static 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]; + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsConstraintDP(n); + 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 new file mode 100644 index 000000000..177b09b11 --- /dev/null +++ b/ja/codes/java/chapter_dynamic_programming/climbing_stairs_dfs.java @@ -0,0 +1,31 @@ +/** + * File: climbing_stairs_dfs.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class climbing_stairs_dfs { + /* 探索 */ + public static 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; + } + + /* 階段登り:探索 */ + public static int climbingStairsDFS(int n) { + return dfs(n); + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsDFS(n); + 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 new file mode 100644 index 000000000..6c4be249c --- /dev/null +++ b/ja/codes/java/chapter_dynamic_programming/climbing_stairs_dfs_mem.java @@ -0,0 +1,41 @@ +/** + * File: climbing_stairs_dfs_mem.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class climbing_stairs_dfs_mem { + /* メモ化探索 */ + public static 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; + } + + /* 階段登り:メモ化探索 */ + public static int climbingStairsDFSMem(int n) { + // mem[i] は i 段目に登る総解数を記録、-1 は記録なしを意味する + int[] mem = new int[n + 1]; + Arrays.fill(mem, -1); + return dfs(n, mem); + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsDFSMem(n); + 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 new file mode 100644 index 000000000..d2be31b55 --- /dev/null +++ b/ja/codes/java/chapter_dynamic_programming/climbing_stairs_dp.java @@ -0,0 +1,48 @@ +/** + * File: climbing_stairs_dp.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class climbing_stairs_dp { + /* 階段登り:動的プログラミング */ + public static 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]; + } + + /* 階段登り:空間最適化動的プログラミング */ + public static 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; + } + + public static void main(String[] args) { + int n = 9; + + int res = climbingStairsDP(n); + System.out.println(String.format("%d段の階段を登る解は%d通りです", n, res)); + + res = climbingStairsDPComp(n); + 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 new file mode 100644 index 000000000..d0478a68c --- /dev/null +++ b/ja/codes/java/chapter_dynamic_programming/coin_change.java @@ -0,0 +1,72 @@ +/** + * File: coin_change.java + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +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テーブルを初期化 + 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; + } + + /* 硬貨両替:空間最適化動的プログラミング */ + static int coinChangeDPComp(int[] coins, int amt) { + int n = coins.length; + int MAX = amt + 1; + // DPテーブルを初期化 + int[] dp = new int[amt + 1]; + Arrays.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; + } + + public static void main(String[] args) { + int[] coins = { 1, 2, 5 }; + int amt = 4; + + // 動的プログラミング + int res = coinChangeDP(coins, amt); + System.out.println("目標金額を作るのに必要な最小硬貨数は " + res + " です"); + + // 空間最適化動的プログラミング + res = coinChangeDPComp(coins, amt); + 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 new file mode 100644 index 000000000..58ec43948 --- /dev/null +++ b/ja/codes/java/chapter_dynamic_programming/coin_change_ii.java @@ -0,0 +1,67 @@ +/** + * File: coin_change_ii.java + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class coin_change_ii { + /* 硬貨両替 II:動的プログラミング */ + static 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 を選択する場合の2つの選択肢の合計 + dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]; + } + } + } + return dp[n][amt]; + } + + /* 硬貨両替 II:空間最適化動的プログラミング */ + static 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 を選択する場合の2つの選択肢の合計 + dp[a] = dp[a] + dp[a - coins[i - 1]]; + } + } + } + return dp[amt]; + } + + public static void main(String[] args) { + int[] coins = { 1, 2, 5 }; + int amt = 5; + + // 動的プログラミング + int res = coinChangeIIDP(coins, amt); + System.out.println("目標金額を作る硬貨の組み合わせ数は " + res + " です"); + + // 空間最適化動的プログラミング + res = coinChangeIIDPComp(coins, amt); + 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 new file mode 100644 index 000000000..bb54e62ac --- /dev/null +++ b/ja/codes/java/chapter_dynamic_programming/edit_distance.java @@ -0,0 +1,139 @@ +/** + * File: edit_distance.java + * Created Time: 2023-07-13 + * Author: krahets (krahets@163.com) + */ + +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 を返す + 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 + 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 を返す + 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 + 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つの文字をスキップ + 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]; + } + + /* 編集距離:空間最適化動的プログラミング */ + 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] を一時的に格納 + 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つの文字をスキップ + 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]; + } + + public static void main(String[] args) { + String s = "bag"; + String t = "pack"; + int n = s.length(), m = t.length(); + + // ブルートフォース探索 + int res = editDistanceDFS(s, t, n, m); + 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 + " 回の編集が必要です"); + + // 動的プログラミング + res = editDistanceDP(s, t); + System.out.println(s + " を " + t + " に変更するには最低 " + res + " 回の編集が必要です"); + + // 空間最適化動的プログラミング + res = editDistanceDPComp(s, t); + 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 new file mode 100644 index 000000000..7826d1223 --- /dev/null +++ b/ja/codes/java/chapter_dynamic_programming/knapsack.java @@ -0,0 +1,116 @@ +/** + * File: knapsack.java + * Created Time: 2023-07-10 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +import java.util.Arrays; + +public class knapsack { + + /* 0-1 ナップサック:ブルートフォース探索 */ + static 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 Math.max(no, yes); + } + + /* 0-1 ナップサック:メモ化探索 */ + static int knapsackDFSMem(int[] wgt, 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 (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] = Math.max(no, yes); + return mem[i][c]; + } + + /* 0-1 ナップサック:動的プログラミング */ + static int knapsackDP(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 - 1][c - wgt[i - 1]] + val[i - 1]); + } + } + } + return dp[n][cap]; + } + + /* 0-1 ナップサック:空間最適化動的プログラミング */ + static int knapsackDPComp(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 = 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]; + } + + public static void main(String[] args) { + int[] wgt = { 10, 20, 30, 40, 50 }; + int[] val = { 50, 120, 150, 210, 240 }; + int cap = 50; + int n = wgt.length; + + // ブルートフォース探索 + int res = knapsackDFS(wgt, val, n, cap); + System.out.println("ナップサック容量内での最大値は " + res + " です"); + + // メモ化探索 + int[][] mem = new int[n + 1][cap + 1]; + for (int[] row : mem) { + Arrays.fill(row, -1); + } + res = knapsackDFSMem(wgt, val, mem, n, cap); + System.out.println("ナップサック容量内での最大値は " + res + " です"); + + // 動的プログラミング + res = knapsackDP(wgt, val, cap); + System.out.println("ナップサック容量内での最大値は " + res + " です"); + + // 空間最適化動的プログラミング + res = knapsackDPComp(wgt, val, cap); + 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 new file mode 100644 index 000000000..3b7b35250 --- /dev/null +++ b/ja/codes/java/chapter_dynamic_programming/min_cost_climbing_stairs_dp.java @@ -0,0 +1,53 @@ +/** + * File: min_cost_climbing_stairs_dp.java + * Created Time: 2023-06-30 + * Author: krahets (krahets@163.com) + */ + +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テーブルを初期化し、部分問題の解を格納するために使用 + 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) + 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; + } + + 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))); + + int res = minCostClimbingStairsDP(cost); + System.out.println(String.format("階段を登るための最小コスト %d", res)); + + res = minCostClimbingStairsDPComp(cost); + 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 new file mode 100644 index 000000000..c7860e1f7 --- /dev/null +++ b/ja/codes/java/chapter_dynamic_programming/min_path_sum.java @@ -0,0 +1,125 @@ +/** + * File: min_path_sum.java + * Created Time: 2023-07-10 + * Author: krahets (krahets@163.com) + */ + +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) への最小パスコストを計算 + int up = minPathSumDFS(grid, i - 1, j); + int left = minPathSumDFS(grid, i, j - 1); + // 左上から (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) への最小パスコストを記録して返す + 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テーブルを初期化 + 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]; + } + + /* 最小パス和:空間最適化動的プログラミング */ + static 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]; + } + + public static void main(String[] args) { + 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); + System.out.println("左上角から右下角への最小パス和は " + res + " です"); + + // メモ化探索 + int[][] mem = new int[n][m]; + for (int[] row : mem) { + Arrays.fill(row, -1); + } + res = minPathSumDFSMem(grid, mem, n - 1, m - 1); + System.out.println("左上角から右下角への最小パス和は " + res + " です"); + + // 動的プログラミング + res = minPathSumDP(grid); + System.out.println("左上角から右下角への最小パス和は " + res + " です"); + + // 空間最適化動的プログラミング + res = minPathSumDPComp(grid); + 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 new file mode 100644 index 000000000..99e7f41f2 --- /dev/null +++ b/ja/codes/java/chapter_dynamic_programming/unbounded_knapsack.java @@ -0,0 +1,63 @@ +/** + * File: unbounded_knapsack.java + * Created Time: 2023-07-11 + * Author: krahets (krahets@163.com) + */ + +package chapter_dynamic_programming; + +public class unbounded_knapsack { + /* 完全ナップサック:動的プログラミング */ + static 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]; + } + + /* 完全ナップサック:空間最適化動的プログラミング */ + static 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]; + } + + public static void main(String[] args) { + int[] wgt = { 1, 2, 3 }; + int[] val = { 5, 11, 15 }; + int cap = 4; + + // 動的プログラミング + int res = unboundedKnapsackDP(wgt, val, cap); + System.out.println("ナップサック容量内での最大値は " + res + " です"); + + // 空間最適化動的プログラミング + res = unboundedKnapsackDPComp(wgt, val, cap); + 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 new file mode 100644 index 000000000..824e73d46 --- /dev/null +++ b/ja/codes/java/chapter_graph/graph_adjacency_list.java @@ -0,0 +1,117 @@ +/** + * File: graph_adjacency_list.java + * Created Time: 2023-01-26 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import java.util.*; +import utils.*; + +/* 隣接リストに基づく無向グラフクラス */ +class GraphAdjList { + // 隣接リスト、キー: 頂点、値: その頂点のすべての隣接頂点 + Map> adjList; + + /* コンストラクタ */ + public GraphAdjList(Vertex[][] edges) { + this.adjList = new HashMap<>(); + // すべての頂点と辺を追加 + for (Vertex[] edge : edges) { + addVertex(edge[0]); + addVertex(edge[1]); + addEdge(edge[0], edge[1]); + } + } + + /* 頂点数を取得 */ + public int size() { + return adjList.size(); + } + + /* 辺を追加 */ + public void addEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw new IllegalArgumentException(); + // 辺 vet1 - vet2 を追加 + adjList.get(vet1).add(vet2); + adjList.get(vet2).add(vet1); + } + + /* 辺を削除 */ + public void removeEdge(Vertex vet1, Vertex vet2) { + if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2) + throw new IllegalArgumentException(); + // 辺 vet1 - vet2 を削除 + adjList.get(vet1).remove(vet2); + adjList.get(vet2).remove(vet1); + } + + /* 頂点を追加 */ + public void addVertex(Vertex vet) { + if (adjList.containsKey(vet)) + return; + // 隣接リストに新しい連結リストを追加 + adjList.put(vet, new ArrayList<>()); + } + + /* 頂点を削除 */ + public void removeVertex(Vertex vet) { + if (!adjList.containsKey(vet)) + throw new IllegalArgumentException(); + // 隣接リストから頂点 vet に対応する連結リストを削除 + adjList.remove(vet); + // 他の頂点の連結リストを走査し、vet を含むすべての辺を削除 + for (List list : adjList.values()) { + list.remove(vet); + } + } + + /* 隣接リストを出力 */ + public void print() { + System.out.println("隣接リスト ="); + for (Map.Entry> pair : adjList.entrySet()) { + List tmp = new ArrayList<>(); + for (Vertex vertex : pair.getValue()) + tmp.add(vertex.val); + System.out.println(pair.getKey().val + ": " + tmp + ","); + } + } +} + +public class graph_adjacency_list { + public static void main(String[] args) { + /* 無向グラフを初期化 */ + Vertex[] v = Vertex.valsToVets(new int[] { 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 GraphAdjList(edges); + System.out.println("\n初期化後、グラフは"); + graph.print(); + + /* 辺を追加 */ + // 頂点 1、2、すなわち v[0]、v[2] + graph.addEdge(v[0], v[2]); + System.out.println("\n辺 1-2 を追加後、グラフは"); + graph.print(); + + /* 辺を削除 */ + // 頂点 1、3、すなわち v[0]、v[1] + graph.removeEdge(v[0], v[1]); + System.out.println("\n辺 1-3 を削除後、グラフは"); + graph.print(); + + /* 頂点を追加 */ + Vertex v5 = new Vertex(6); + graph.addVertex(v5); + System.out.println("\n頂点 6 を追加後、グラフは"); + graph.print(); + + /* 頂点を削除 */ + // 頂点 3、すなわち v[1] + graph.removeVertex(v[1]); + 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 new file mode 100644 index 000000000..55755d0b1 --- /dev/null +++ b/ja/codes/java/chapter_graph/graph_adjacency_matrix.java @@ -0,0 +1,131 @@ +/** + * File: graph_adjacency_matrix.java + * Created Time: 2023-01-26 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import utils.*; +import java.util.*; + +/* 隣接行列に基づく無向グラフクラス */ +class GraphAdjMat { + List vertices; // 頂点リスト、要素は「頂点値」を表し、インデックスは「頂点インデックス」を表す + List> adjMat; // 隣接行列、行と列のインデックスは「頂点インデックス」に対応 + + /* コンストラクタ */ + public GraphAdjMat(int[] vertices, int[][] edges) { + this.vertices = new ArrayList<>(); + this.adjMat = new ArrayList<>(); + // 頂点を追加 + for (int val : vertices) { + addVertex(val); + } + // 辺を追加 + // 辺の要素は頂点インデックスを表す + for (int[] e : edges) { + addEdge(e[0], e[1]); + } + } + + /* 頂点数を取得 */ + public int size() { + return vertices.size(); + } + + /* 頂点を追加 */ + public void addVertex(int val) { + int n = size(); + // 頂点リストに新しい頂点値を追加 + vertices.add(val); + // 隣接行列に行を追加 + List newRow = new ArrayList<>(n); + for (int j = 0; j < n; j++) { + newRow.add(0); + } + adjMat.add(newRow); + // 隣接行列に列を追加 + for (List row : adjMat) { + row.add(0); + } + } + + /* 頂点を削除 */ + public void removeVertex(int index) { + if (index >= size()) + throw new IndexOutOfBoundsException(); + // 頂点リストから `index` の頂点を削除 + vertices.remove(index); + // 隣接行列から `index` の行を削除 + adjMat.remove(index); + // 隣接行列から `index` の列を削除 + for (List row : adjMat) { + row.remove(index); + } + } + + /* 辺を追加 */ + // パラメータ i、j は頂点要素のインデックスに対応 + 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) を満たす + adjMat.get(i).set(j, 1); + adjMat.get(j).set(i, 1); + } + + /* 辺を削除 */ + // パラメータ i、j は頂点要素のインデックスに対応 + 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); + adjMat.get(j).set(i, 0); + } + + /* 隣接行列を出力 */ + public void print() { + System.out.print("頂点リスト = "); + System.out.println(vertices); + System.out.println("隣接行列 ="); + PrintUtil.printMatrix(adjMat); + } +} + +public class graph_adjacency_matrix { + public static void main(String[] args) { + /* 無向グラフを初期化 */ + // 辺の要素は頂点インデックスを表す + 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初期化後、グラフは"); + graph.print(); + + /* 辺を追加 */ + // 頂点 1、2 のインデックスはそれぞれ 0、2 + graph.addEdge(0, 2); + System.out.println("\n辺 1-2 を追加後、グラフは"); + graph.print(); + + /* 辺を削除 */ + // 頂点 1、3 のインデックスはそれぞれ 0、1 + graph.removeEdge(0, 1); + System.out.println("\n辺 1-3 を削除後、グラフは"); + graph.print(); + + /* 頂点を追加 */ + graph.addVertex(6); + System.out.println("\n頂点 6 を追加後、グラフは"); + graph.print(); + + /* 頂点を削除 */ + // 頂点 3 のインデックスは 1 + graph.removeVertex(1); + 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 new file mode 100644 index 000000000..eca03f12e --- /dev/null +++ b/ja/codes/java/chapter_graph/graph_bfs.java @@ -0,0 +1,55 @@ +/** + * File: graph_bfs.java + * Created Time: 2023-02-12 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +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 を実装するために使用するキュー + Queue que = new LinkedList<>(); + que.offer(startVet); + // 頂点 vet から開始し、すべての頂点が訪問されるまでループ + while (!que.isEmpty()) { + 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); // 頂点を訪問済みとしてマーク + } + } + // 頂点走査順序を返す + return res; + } + + public static void main(String[] args) { + /* 無向グラフを初期化 */ + Vertex[] v = Vertex.valsToVets(new int[] { 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 GraphAdjList(edges); + System.out.println("\n初期化後、グラフは"); + graph.print(); + + /* 幅優先走査 */ + List res = graphBFS(graph, v[0]); + 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 new file mode 100644 index 000000000..f8619af18 --- /dev/null +++ b/ja/codes/java/chapter_graph/graph_dfs.java @@ -0,0 +1,51 @@ +/** + * File: graph_dfs.java + * Created Time: 2023-02-12 + * Author: krahets (krahets@163.com) + */ + +package chapter_graph; + +import java.util.*; +import utils.*; + +public class graph_dfs { + /* 深さ優先走査の補助関数 */ + static void dfs(GraphAdjList graph, Set visited, List res, Vertex vet) { + res.add(vet); // 訪問した頂点を記録 + visited.add(vet); // 頂点を訪問済みとしてマーク + // その頂点のすべての隣接頂点を走査 + for (Vertex adjVet : graph.adjList.get(vet)) { + if (visited.contains(adjVet)) + 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; + } + + public static void main(String[] args) { + /* 無向グラフを初期化 */ + Vertex[] v = Vertex.valsToVets(new int[] { 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 GraphAdjList(edges); + System.out.println("\n初期化後、グラフは"); + graph.print(); + + /* 深さ優先走査 */ + List res = graphDFS(graph, v[0]); + 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 new file mode 100644 index 000000000..fef56080d --- /dev/null +++ b/ja/codes/java/chapter_greedy/coin_change_greedy.java @@ -0,0 +1,55 @@ +/** + * File: coin_change_greedy.java + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +import java.util.Arrays; + +public class coin_change_greedy { + /* 硬貨両替:貪欲法 */ + static int coinChangeGreedy(int[] coins, int amt) { + // 硬貨リストが順序付けされていると仮定 + 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; + } + + 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 + " です"); + + // 貪欲法:大域最適解の発見を保証できない + 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"); + + // 貪欲法:大域最適解の発見を保証できない + 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"); + } +} \ 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 new file mode 100644 index 000000000..37c5298eb --- /dev/null +++ b/ja/codes/java/chapter_greedy/fractional_knapsack.java @@ -0,0 +1,59 @@ +/** + * File: fractional_knapsack.java + * Created Time: 2023-07-20 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +import java.util.Arrays; +import java.util.Comparator; + +/* アイテム */ +class Item { + int w; // アイテムの重量 + int v; // アイテムの価値 + + public Item(int w, int v) { + this.w = w; + this.v = v; + } +} + +public class fractional_knapsack { + /* 分数ナップサック:貪欲法 */ + static 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 で高い順にソート + 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; + } + } + return res; + } + + public static void main(String[] args) { + int[] wgt = { 10, 20, 30, 40, 50 }; + int[] val = { 50, 120, 150, 210, 240 }; + int cap = 50; + + // 貪欲アルゴリズム + double res = fractionalKnapsack(wgt, val, cap); + 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 new file mode 100644 index 000000000..01b7ea6f5 --- /dev/null +++ b/ja/codes/java/chapter_greedy/max_capacity.java @@ -0,0 +1,38 @@ +/** + * File: max_capacity.java + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +public class max_capacity { + /* 最大容量:貪欲法 */ + static 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; + } + + public static void main(String[] args) { + int[] ht = { 3, 8, 5, 2, 7, 7, 3, 4 }; + + // 貪欲アルゴリズム + int res = maxCapacity(ht); + 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 new file mode 100644 index 000000000..f10de127d --- /dev/null +++ b/ja/codes/java/chapter_greedy/max_product_cutting.java @@ -0,0 +1,40 @@ +/** + * File: max_product_cutting.java + * Created Time: 2023-07-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_greedy; + +import java.lang.Math; + +public class max_product_cutting { + /* 最大積切断:貪欲法 */ + public static 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); + } + + public static void main(String[] args) { + int n = 58; + + // 貪欲アルゴリズム + int res = maxProductCutting(n); + 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 new file mode 100644 index 000000000..f62b450fb --- /dev/null +++ b/ja/codes/java/chapter_hashing/array_hash_map.java @@ -0,0 +1,141 @@ +/** + * File: array_hash_map.java + * Created Time: 2022-12-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import java.util.*; + +/* キー値ペア */ +class Pair { + public int key; + public String val; + + public Pair(int key, String val) { + this.key = key; + this.val = val; + } +} + +/* 配列実装に基づくハッシュテーブル */ +class ArrayHashMap { + private List buckets; + + public ArrayHashMap() { + // 100個のバケットを含む配列を初期化 + buckets = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + buckets.add(null); + } + } + + /* ハッシュ関数 */ + private int hashFunc(int key) { + int index = key % 100; + return index; + } + + /* クエリ操作 */ + public String get(int key) { + int index = hashFunc(key); + Pair pair = buckets.get(index); + if (pair == null) + return null; + return pair.val; + } + + /* 追加操作 */ + public void put(int key, String val) { + Pair pair = new Pair(key, val); + int index = hashFunc(key); + buckets.set(index, pair); + } + + /* 削除操作 */ + public void remove(int key) { + int index = hashFunc(key); + // nullに設定して削除を示す + buckets.set(index, null); + } + + /* すべてのキー値ペアを取得 */ + public List pairSet() { + List pairSet = new ArrayList<>(); + for (Pair pair : buckets) { + if (pair != null) + pairSet.add(pair); + } + return pairSet; + } + + /* すべてのキーを取得 */ + public List keySet() { + List keySet = new ArrayList<>(); + for (Pair pair : buckets) { + if (pair != null) + keySet.add(pair.key); + } + return keySet; + } + + /* すべての値を取得 */ + public List valueSet() { + List valueSet = new ArrayList<>(); + for (Pair pair : buckets) { + if (pair != null) + valueSet.add(pair.val); + } + return valueSet; + } + + /* ハッシュテーブルを印刷 */ + public void print() { + for (Pair kv : pairSet()) { + System.out.println(kv.key + " -> " + kv.val); + } + } +} + +public class array_hash_map { + public static void main(String[] args) { + /* ハッシュテーブルを初期化 */ + 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"); + map.print(); + + /* クエリ操作 */ + // ハッシュテーブルにキーを入力して値を取得 + String name = map.get(15937); + System.out.println("\n学生ID 15937を入力、名前 " + name + " を見つけました"); + + /* 削除操作 */ + // ハッシュテーブルからキー値ペア (key, value) を削除 + map.remove(10583); + System.out.println("\n10583を削除後のハッシュテーブル\nKey -> Value"); + map.print(); + + /* ハッシュテーブルを走査 */ + System.out.println("\nキー値ペアを走査 Key->Value"); + for (Pair kv : map.pairSet()) { + System.out.println(kv.key + " -> " + kv.val); + } + System.out.println("\nキーを個別に走査 Key"); + for (int key : map.keySet()) { + System.out.println(key); + } + 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 new file mode 100644 index 000000000..738c70b13 --- /dev/null +++ b/ja/codes/java/chapter_hashing/built_in_hash.java @@ -0,0 +1,38 @@ +/** + * File: built_in_hash.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import utils.*; +import java.util.*; + +public class built_in_hash { + public static void main(String[] args) { + int num = 3; + int hashNum = Integer.hashCode(num); + System.out.println("整数 " + num + " のハッシュ値は " + hashNum + " です"); + + boolean bol = true; + int hashBol = Boolean.hashCode(bol); + System.out.println("ブール値 " + bol + " のハッシュ値は " + hashBol + " です"); + + double dec = 3.14159; + int hashDec = Double.hashCode(dec); + System.out.println("小数 " + dec + " のハッシュ値は " + hashDec + " です"); + + String str = "Hello algorithm"; + int hashStr = str.hashCode(); + System.out.println("文字列 " + str + " のハッシュ値は " + hashStr + " です"); + + Object[] arr = { 12836, "Ha" }; + int hashTup = Arrays.hashCode(arr); + System.out.println("配列 " + Arrays.toString(arr) + " のハッシュ値は " + hashTup + " です"); + + ListNode obj = new ListNode(0); + int hashObj = obj.hashCode(); + 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 new file mode 100644 index 000000000..6411b5cb4 --- /dev/null +++ b/ja/codes/java/chapter_hashing/hash_map.java @@ -0,0 +1,52 @@ +/** + * File: hash_map.java + * Created Time: 2022-12-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import java.util.*; +import utils.*; + +public class hash_map { + public static void main(String[] args) { + /* ハッシュテーブルを初期化 */ + 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"); + PrintUtil.printHashMap(map); + + /* 検索操作 */ + // ハッシュテーブルにキーを入力し、値を取得 + String name = map.get(15937); + System.out.println("\n学生番号 15937 を入力し、名前 " + name + " を見つけました"); + + /* 削除操作 */ + // ハッシュテーブルからキー値ペア (key, value) を削除 + map.remove(10583); + System.out.println("\n10583 を削除後、ハッシュテーブルは\nKey -> Value"); + PrintUtil.printHashMap(map); + + /* ハッシュテーブルの走査 */ + System.out.println("\nキー値ペアを走査 Key->Value"); + for (Map.Entry kv : map.entrySet()) { + System.out.println(kv.getKey() + " -> " + kv.getValue()); + } + System.out.println("\nキーを個別に走査 Key"); + for (int key : map.keySet()) { + System.out.println(key); + } + 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 new file mode 100644 index 000000000..60113ee36 --- /dev/null +++ b/ja/codes/java/chapter_hashing/hash_map_chaining.java @@ -0,0 +1,148 @@ +/** + * File: hash_map_chaining.java + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +import java.util.ArrayList; +import java.util.List; + +/* チェイン法ハッシュテーブル */ +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 ArrayList<>(capacity); + for (int i = 0; i < capacity; i++) { + buckets.add(new ArrayList<>()); + } + } + + /* ハッシュ関数 */ + int hashFunc(int key) { + return key % capacity; + } + + /* 負荷率 */ + double loadFactor() { + return (double) size / capacity; + } + + /* クエリ操作 */ + String get(int key) { + int index = hashFunc(key); + List bucket = buckets.get(index); + // バケットを走査、キーが見つかった場合対応するvalを返す + for (Pair pair : bucket) { + if (pair.key == key) { + return pair.val; + } + } + // キーが見つからない場合、nullを返す + return null; + } + + /* 追加操作 */ + void put(int key, String val) { + // 負荷率が閾値を超えた場合、拡張を実行 + if (loadFactor() > loadThres) { + extend(); + } + int index = hashFunc(key); + List bucket = buckets.get(index); + // バケットを走査、指定したキーに遭遇した場合、対応するvalを更新して戻る + for (Pair pair : bucket) { + if (pair.key == key) { + pair.val = val; + return; + } + } + // キーが見つからない場合、キー値ペアを末尾に追加 + Pair pair = new Pair(key, val); + bucket.add(pair); + size++; + } + + /* 削除操作 */ + void remove(int key) { + int index = hashFunc(key); + List bucket = buckets.get(index); + // バケットを走査、その中からキー値ペアを削除 + for (Pair pair : bucket) { + if (pair.key == key) { + bucket.remove(pair); + size--; + break; + } + } + } + + /* ハッシュテーブルを拡張 */ + 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); + } + } + } + + /* ハッシュテーブルを印刷 */ + void print() { + for (List bucket : buckets) { + List res = new ArrayList<>(); + for (Pair pair : bucket) { + res.add(pair.key + " -> " + pair.val); + } + System.out.println(res); + } + } +} + +public class hash_map_chaining { + public static void main(String[] args) { + /* ハッシュテーブルを初期化 */ + 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"); + map.print(); + + /* クエリ操作 */ + // ハッシュテーブルにキーを入力して値を取得 + String name = map.get(13276); + System.out.println("\n学生ID 13276を入力、名前 " + name + " を見つけました"); + + /* 削除操作 */ + // ハッシュテーブルからキー値ペア (key, value) を削除 + map.remove(12836); + 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 new file mode 100644 index 000000000..2c7c64791 --- /dev/null +++ b/ja/codes/java/chapter_hashing/hash_map_open_addressing.java @@ -0,0 +1,158 @@ +/** + * File: hash_map_open_addressing.java + * Created Time: 2023-06-13 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +/* オープンアドレス法ハッシュテーブル */ +class HashMapOpenAddressing { + 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"); // 削除マーク + + /* コンストラクタ */ + public HashMapOpenAddressing() { + size = 0; + buckets = new Pair[capacity]; + } + + /* ハッシュ関数 */ + private int hashFunc(int key) { + return key % capacity; + } + + /* 負荷率 */ + private double loadFactor() { + return (double) size / capacity; + } + + /* keyに対応するバケットインデックスを検索 */ + private 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--; + } + } + + /* ハッシュテーブルを拡張 */ + 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); + } + } + } + + /* ハッシュテーブルを印刷 */ + public void print() { + for (Pair pair : buckets) { + if (pair == null) { + System.out.println("null"); + } else if (pair == TOMBSTONE) { + System.out.println("TOMBSTONE"); + } else { + System.out.println(pair.key + " -> " + pair.val); + } + } + } +} + +public class hash_map_open_addressing { + public static void main(String[] args) { + // ハッシュテーブルを初期化 + 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"); + hashmap.print(); + + // クエリ操作 + // ハッシュテーブルにキーを入力して値valを取得 + String name = hashmap.get(13276); + System.out.println("\n学生ID 13276を入力、名前 " + name + " を見つけました"); + + // 削除操作 + // ハッシュテーブルからキー値ペア (key, val) を削除 + hashmap.remove(16750); + 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 new file mode 100644 index 000000000..d83823655 --- /dev/null +++ b/ja/codes/java/chapter_hashing/simple_hash.java @@ -0,0 +1,65 @@ +/** + * File: simple_hash.java + * Created Time: 2023-06-21 + * Author: krahets (krahets@163.com) + */ + +package chapter_hashing; + +public class simple_hash { + /* 加算ハッシュ */ + static int addHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = (hash + (int) c) % MODULUS; + } + return (int) hash; + } + + /* 乗算ハッシュ */ + static int mulHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = (31 * hash + (int) c) % MODULUS; + } + return (int) hash; + } + + /* XORハッシュ */ + static int xorHash(String key) { + int hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash ^= (int) c; + } + return hash & MODULUS; + } + + /* 回転ハッシュ */ + static int rotHash(String key) { + long hash = 0; + final int MODULUS = 1000000007; + for (char c : key.toCharArray()) { + hash = ((hash << 4) ^ (hash >> 28) ^ (int) c) % MODULUS; + } + return (int) hash; + } + + public static void main(String[] args) { + String key = "Hello algorithm"; + + int hash = addHash(key); + System.out.println("加算ハッシュ値は " + hash + " です"); + + hash = mulHash(key); + System.out.println("乗算ハッシュ値は " + hash + " です"); + + hash = xorHash(key); + System.out.println("XORハッシュ値は " + hash + " です"); + + hash = rotHash(key); + 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 new file mode 100644 index 000000000..25b2a0bdc --- /dev/null +++ b/ja/codes/java/chapter_heap/heap.java @@ -0,0 +1,66 @@ +/** + * File: heap.java + * Created Time: 2023-01-07 + * Author: krahets (krahets@163.com) + */ + +package chapter_heap; + +import utils.*; +import java.util.*; + +public class heap { + public static void testPush(Queue heap, int 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); + PrintUtil.printHeap(heap); + } + + public static void main(String[] args) { + /* ヒープを初期化 */ + // 最小ヒープを初期化 + Queue minHeap = new PriorityQueue<>(); + // 最大ヒープを初期化(必要に応じてラムダ式を使用してComparatorを変更) + Queue maxHeap = new PriorityQueue<>((a, b) -> b - a); + + 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); + + /* ヒープの先頭要素をポップ */ + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + testPop(maxHeap); + + /* ヒープのサイズを取得 */ + int size = maxHeap.size(); + System.out.format("\nヒープ内の要素数は %d\n", size); + + /* ヒープが空かどうかを判定 */ + boolean isEmpty = maxHeap.isEmpty(); + System.out.format("\nヒープは空ですか %b\n", isEmpty); + + /* リストを入力してヒープを構築 */ + // 時間計算量は O(n)、O(nlogn) ではない + minHeap = new PriorityQueue<>(Arrays.asList(1, 3, 2, 5, 4)); + 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 new file mode 100644 index 000000000..21fd13c08 --- /dev/null +++ b/ja/codes/java/chapter_heap/my_heap.java @@ -0,0 +1,159 @@ +/** + * File: my_heap.java + * Created Time: 2023-01-07 + * Author: krahets (krahets@163.com) + */ + +package chapter_heap; + +import utils.*; +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; // 整数除算で切り下げ + } + + /* 要素を交換 */ + private void swap(int i, int j) { + int tmp = maxHeap.get(i); + maxHeap.set(i, maxHeap.get(j)); + maxHeap.set(j, tmp); + } + + /* ヒープのサイズを取得 */ + public int size() { + return maxHeap.size(); + } + + /* ヒープが空かどうかを判定 */ + public boolean isEmpty() { + return size() == 0; + } + + /* ヒープの先頭要素にアクセス */ + public int peek() { + return maxHeap.get(0); + } + + /* 要素をヒープにプッシュ */ + public void push(int val) { + // ノードを追加 + maxHeap.add(val); + // 下から上へヒープ化 + siftUp(size() - 1); + } + + /* ノード i から上向きにヒープ化を開始 */ + private void siftUp(int i) { + while (true) { + // ノード i の親ノードを取得 + int p = parent(i); + // 「根ノードを越える」または「ノードが修復不要」の場合、ヒープ化を終了 + if (p < 0 || maxHeap.get(i) <= maxHeap.get(p)) + break; + // 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 から下向きにヒープ化を開始 */ + private void siftDown(int i) { + while (true) { + // 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 が範囲外の場合、さらなるヒープ化は不要、終了 + if (ma == i) + break; + // 2つのノードを交換 + swap(i, ma); + // 下向きにヒープ化をループ + i = ma; + } + } + + /* ヒープ(二分木)を出力 */ + public void print() { + Queue queue = new PriorityQueue<>((a, b) -> { return b - a; }); + queue.addAll(maxHeap); + PrintUtil.printHeap(queue); + } +} + +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リストを入力してヒープを構築"); + maxHeap.print(); + + /* ヒープの先頭要素にアクセス */ + int peek = maxHeap.peek(); + System.out.format("\nヒープの先頭要素は %d\n", peek); + + /* 要素をヒープにプッシュ */ + int val = 7; + maxHeap.push(val); + System.out.format("\n要素 %d をヒープに追加後\n", val); + maxHeap.print(); + + /* ヒープの先頭要素をポップ */ + peek = maxHeap.pop(); + System.out.format("\n先頭要素 %d をヒープから削除後\n", peek); + maxHeap.print(); + + /* ヒープのサイズを取得 */ + int size = maxHeap.size(); + System.out.format("\nヒープ内の要素数は %d\n", size); + + /* ヒープが空かどうかを判定 */ + boolean isEmpty = maxHeap.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 new file mode 100644 index 000000000..749d836f7 --- /dev/null +++ b/ja/codes/java/chapter_heap/top_k.java @@ -0,0 +1,40 @@ +/** + * File: top_k.java + * Created Time: 2023-06-12 + * Author: krahets (krahets@163.com) + */ + +package chapter_heap; + +import utils.*; +import java.util.*; + +public class top_k { + /* ヒープを使用して配列内の最大 k 個の要素を検索 */ + static Queue topKHeap(int[] nums, int k) { + // 最小ヒープを初期化 + Queue heap = new PriorityQueue(); + // 配列の最初の k 個の要素をヒープに入力 + for (int i = 0; i < k; i++) { + heap.offer(nums[i]); + } + // k+1 番目の要素から、ヒープの長さを k に保つ + for (int i = k; i < nums.length; i++) { + // 現在の要素がヒープの先頭要素より大きい場合、ヒープの先頭要素を削除し、現在の要素をヒープに入力 + if (nums[i] > heap.peek()) { + heap.poll(); + heap.offer(nums[i]); + } + } + return heap; + } + + public static void main(String[] args) { + int[] nums = { 1, 7, 6, 3, 2 }; + int k = 3; + + Queue res = topKHeap(nums, 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 new file mode 100644 index 000000000..3615ffe1e --- /dev/null +++ b/ja/codes/java/chapter_searching/binary_search.java @@ -0,0 +1,58 @@ +/** + * File: binary_search.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +public class binary_search { + /* 二分探索(両端閉区間) */ + static 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; + } + + /* 二分探索(左閉右開区間) */ + static 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; + } + + public static void main(String[] args) { + 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); + + /* 二分探索(左閉右開区間) */ + index = binarySearchLCRO(nums, target); + 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 new file mode 100644 index 000000000..5a13abd31 --- /dev/null +++ b/ja/codes/java/chapter_searching/binary_search_edge.java @@ -0,0 +1,49 @@ +/** + * File: binary_search_edge.java + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +public class binary_search_edge { + /* 最も左の target を二分探索 */ + static 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 を二分探索 */ + static 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; + } + + public static void main(String[] args) { + // 重複要素を含む配列 + 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); + index = binarySearchRightEdge(nums, target); + 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 new file mode 100644 index 000000000..5f78fb4ac --- /dev/null +++ b/ja/codes/java/chapter_searching/binary_search_insertion.java @@ -0,0 +1,63 @@ +/** + * File: binary_search_insertion.java + * Created Time: 2023-08-04 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +class binary_search_insertion { + /* 挿入点の二分探索(重複要素なし) */ + 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; + } + + /* 挿入点の二分探索(重複要素あり) */ + 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; + } + + public static void main(String[] args) { + // 重複要素のない配列 + 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); + } + + // 重複要素のある配列 + 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); + } + } +} \ 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 new file mode 100644 index 000000000..c31fc10f2 --- /dev/null +++ b/ja/codes/java/chapter_searching/hashing_search.java @@ -0,0 +1,51 @@ +/** + * File: hashing_search.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +import utils.*; +import java.util.*; + +public class hashing_search { + /* ハッシュ探索(配列) */ + static int hashingSearchArray(Map map, int target) { + // ハッシュテーブルのキー: 目標要素、値: インデックス + // ハッシュテーブルにこのキーが含まれていない場合、-1 を返す + return map.getOrDefault(target, -1); + } + + /* ハッシュ探索(連結リスト) */ + static ListNode hashingSearchLinkedList(Map map, int target) { + // ハッシュテーブルのキー: 目標ノードの値、値: ノードオブジェクト + // キーがハッシュテーブルにない場合、null を返す + return map.getOrDefault(target, null); + } + + public static void main(String[] args) { + int target = 3; + + /* ハッシュ探索(配列) */ + int[] nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; + // ハッシュテーブルを初期化 + Map map = new HashMap<>(); + for (int i = 0; i < nums.length; i++) { + map.put(nums[i], i); // キー: 要素、値: インデックス + } + int index = hashingSearchArray(map, target); + System.out.println("目標要素 3 のインデックスは " + index); + + /* ハッシュ探索(連結リスト) */ + ListNode head = ListNode.arrToLinkedList(nums); + // ハッシュテーブルを初期化 + Map map1 = new HashMap<>(); + while (head != null) { + map1.put(head.val, head); // キー: ノードの値、値: ノード + head = head.next; + } + ListNode node = hashingSearchLinkedList(map1, target); + 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 new file mode 100644 index 000000000..3ad624aec --- /dev/null +++ b/ja/codes/java/chapter_searching/linear_search.java @@ -0,0 +1,50 @@ +/** + * File: linear_search.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +import utils.*; + +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 を返す + return -1; + } + + /* 線形探索(連結リスト) */ + static ListNode linearSearchLinkedList(ListNode head, int target) { + // リストを走査 + while (head != null) { + // 目標ノードを見つけたので、それを返す + if (head.val == target) + return head; + head = head.next; + } + // 目標ノードが見つからない場合、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); + + /* 連結リストで線形探索を実行 */ + ListNode head = ListNode.arrToLinkedList(nums); + ListNode node = linearSearchLinkedList(head, target); + 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 new file mode 100644 index 000000000..2485d5f7f --- /dev/null +++ b/ja/codes/java/chapter_searching/two_sum.java @@ -0,0 +1,53 @@ +/** + * File: two_sum.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_searching; + +import java.util.*; + +public class two_sum { + /* 方法一: 暴力列挙 */ + static int[] twoSumBruteForce(int[] nums, int target) { + int size = nums.length; + // 二重ループ、時間計算量は 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 new int[] { i, j }; + } + } + return new int[0]; + } + + /* 方法二: 補助ハッシュテーブル */ + static int[] twoSumHashTable(int[] nums, int target) { + int size = nums.length; + // 補助ハッシュテーブル、空間計算量は O(n) + Map dic = new HashMap<>(); + // 単一層ループ、時間計算量は O(n) + for (int i = 0; i < size; i++) { + if (dic.containsKey(target - nums[i])) { + return new int[] { dic.get(target - nums[i]), i }; + } + dic.put(nums[i], i); + } + return new int[0]; + } + + public static void main(String[] args) { + // ======= テストケース ======= + int[] nums = { 2, 7, 11, 15 }; + int target = 13; + + // ====== ドライバーコード ====== + // 方法一 + int[] res = twoSumBruteForce(nums, target); + System.out.println("方法一 res = " + Arrays.toString(res)); + // 方法二 + res = twoSumHashTable(nums, target); + System.out.println("方法二 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 new file mode 100644 index 000000000..4bb0fce85 --- /dev/null +++ b/ja/codes/java/chapter_sorting/bubble_sort.java @@ -0,0 +1,57 @@ +/** + * File: bubble_sort.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class bubble_sort { + /* バブルソート */ + static 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] を交換 + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } + } + + /* バブルソート(フラグによる最適化) */ + static void bubbleSortWithFlag(int[] nums) { + // 外側ループ: 未ソート範囲は [0, i] + for (int i = nums.length - 1; i > 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; // 交換された要素を記録 + } + } + if (!flag) + break; // この「バブリング」ラウンドで要素が交換されなかった場合、終了 + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + bubbleSort(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)); + } +} \ 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 new file mode 100644 index 000000000..7552fd245 --- /dev/null +++ b/ja/codes/java/chapter_sorting/bucket_sort.java @@ -0,0 +1,47 @@ +/** + * File: bucket_sort.java + * Created Time: 2023-03-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class bucket_sort { + /* バケットソート */ + static void bucketSort(float[] nums) { + // 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. 配列要素を各バケットに分散 + for (float num : nums) { + // 入力データ範囲は [0, 1)、num * k を使ってインデックス範囲 [0, k-1] にマッピング + int i = (int) (num * k); + // num をバケット i に追加 + buckets.get(i).add(num); + } + // 2. 各バケットをソート + for (List bucket : buckets) { + // 組み込みソート関数を使用、他のソートアルゴリズムに置き換えることも可能 + Collections.sort(bucket); + } + // 3. バケットを走査して結果をマージ + int i = 0; + for (List bucket : buckets) { + for (float num : bucket) { + nums[i++] = num; + } + } + } + + public static void main(String[] args) { + // 入力データが浮動小数点、範囲 [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)); + } +} \ 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 new file mode 100644 index 000000000..fdeb1fc29 --- /dev/null +++ b/ja/codes/java/chapter_sorting/counting_sort.java @@ -0,0 +1,78 @@ +/** + * File: counting_sort.java + * Created Time: 2023-03-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class counting_sort { + /* 計数ソート */ + // 簡単な実装、オブジェクトのソートには使用できない + static void countingSortNaive(int[] nums) { + // 1. 配列の最大要素 m を統計 + int m = 0; + for (int num : nums) { + m = Math.max(m, num); + } + // 2. 各数字の出現回数を統計 + // counter[num] は num の出現回数を表す + int[] counter = new int[m + 1]; + for (int num : 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; + } + } + } + + /* 計数ソート */ + // 完全な実装、オブジェクトをソートでき、安定ソート + static void countingSort(int[] nums) { + // 1. 配列の最大要素 m を統計 + int m = 0; + for (int num : nums) { + m = Math.max(m, num); + } + // 2. 各数字の出現回数を統計 + // counter[num] は num の出現回数を表す + int[] counter = new int[m + 1]; + for (int num : 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; + 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]; + } + } + + 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)); + + int[] nums1 = { 1, 0, 1, 2, 0, 4, 0, 2, 2, 4 }; + countingSort(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 new file mode 100644 index 000000000..f6efa1966 --- /dev/null +++ b/ja/codes/java/chapter_sorting/heap_sort.java @@ -0,0 +1,57 @@ +/** + * File: heap_sort.java + * Created Time: 2023-05-26 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.Arrays; + +public class heap_sort { + /* ヒープの長さは n、ノード i から上から下へヒープ化開始 */ + public static 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つのノードを交換 + 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 回繰り返し + for (int i = nums.length - 1; i > 0; i--) { + // ルートノードと最も右の葉ノードを交換(最初の要素と最後の要素を交換) + int tmp = nums[0]; + nums[0] = nums[i]; + nums[i] = tmp; + // ルートノードから上から下へヒープ化開始 + siftDown(nums, i, 0); + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + heapSort(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 new file mode 100644 index 000000000..b54993f85 --- /dev/null +++ b/ja/codes/java/chapter_sorting/insertion_sort.java @@ -0,0 +1,31 @@ +/** + * File: insertion_sort.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class insertion_sort { + /* 挿入ソート */ + static void insertionSort(int[] 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 を正しい位置に代入 + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + insertionSort(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 new file mode 100644 index 000000000..72aa5a772 --- /dev/null +++ b/ja/codes/java/chapter_sorting/merge_sort.java @@ -0,0 +1,58 @@ +/** + * File: merge_sort.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class merge_sort { + /* 左部分配列と右部分配列をマージ */ + static 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]; + } + } + + /* マージソート */ + static 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); + } + + public static void main(String[] args) { + /* マージソート */ + int[] nums = { 7, 3, 2, 6, 0, 1, 5, 4 }; + mergeSort(nums, 0, nums.length - 1); + 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 new file mode 100644 index 000000000..4a688cd74 --- /dev/null +++ b/ja/codes/java/chapter_sorting/quick_sort.java @@ -0,0 +1,158 @@ +/** + * File: quick_sort.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +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] を基準値として使用 + 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) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 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) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 分割 */ + 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 { + public static void main(String[] args) { + /* クイックソート */ + int[] nums = { 2, 4, 1, 0, 3, 5 }; + QuickSort.quickSort(nums, 0, nums.length - 1); + 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)); + + /* クイックソート(末尾再帰最適化) */ + int[] nums2 = { 2, 4, 1, 0, 3, 5 }; + QuickSortTailCall.quickSort(nums2, 0, nums2.length - 1); + 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 new file mode 100644 index 000000000..2de1eeac0 --- /dev/null +++ b/ja/codes/java/chapter_sorting/radix_sort.java @@ -0,0 +1,69 @@ +/** + * File: radix_sort.java + * Created Time: 2023-01-17 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.*; + +public class radix_sort { + /* 要素 num の k 番目の桁を取得、exp = 10^(k-1) */ + static int digit(int num, int exp) { + // k の代わりに exp を渡すことで、ここでコストの高い累乗計算の繰り返しを避けることができる + return (num / exp) % 10; + } + + /* 計数ソート(nums の k 番目の桁に基づく) */ + static 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]; + } + + /* 基数ソート */ + 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 = 1 -> exp = 1 + // k = 2 -> exp = 10 + // すなわち exp = 10^(k-1) + countingSortDigit(nums, exp); + } + } + + public static void main(String[] args) { + // 基数ソート + int[] nums = { 10546151, 35663510, 42865989, 34862445, 81883077, + 88906420, 72429244, 30524779, 82060337, 63832996 }; + radixSort(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 new file mode 100644 index 000000000..76abb5bb9 --- /dev/null +++ b/ja/codes/java/chapter_sorting/selection_sort.java @@ -0,0 +1,35 @@ +/** + * File: selection_sort.java + * Created Time: 2023-05-23 + * Author: krahets (krahets@163.com) + */ + +package chapter_sorting; + +import java.util.Arrays; + +public class selection_sort { + /* 選択ソート */ + public static 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; // 最小要素のインデックスを記録 + } + // 最小要素と未ソート範囲の最初の要素を交換 + int temp = nums[i]; + nums[i] = nums[k]; + nums[k] = temp; + } + } + + public static void main(String[] args) { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + selectionSort(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 new file mode 100644 index 000000000..5ea40e4fa --- /dev/null +++ b/ja/codes/java/chapter_stack_and_queue/array_deque.java @@ -0,0 +1,151 @@ +/** + * File: array_deque.java + * Created Time: 2023-02-16 + * Author: krahets (krahets@163.com), FangYuan33 (374072213@qq.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* 循環配列に基づく両端キュークラス */ +class ArrayDeque { + private int[] nums; // 両端キューの要素を格納する配列 + private int front; // 先頭ポインタ、先頭要素を指す + private int queSize; // 両端キューの長さ + + /* コンストラクタ */ + public ArrayDeque(int capacity) { + this.nums = new int[capacity]; + front = queSize = 0; + } + + /* 両端キューの容量を取得 */ + public int capacity() { + return nums.length; + } + + /* 両端キューの長さを取得 */ + public int size() { + return queSize; + } + + /* 両端キューが空かどうかを判定 */ + public boolean isEmpty() { + return queSize == 0; + } + + /* 循環配列インデックスを計算 */ + private int index(int i) { + // モジュロ演算により循環配列を実装 + // i が配列の末尾を超える場合、先頭に戻る + // i が配列の先頭を超える場合、末尾に戻る + return (i + capacity()) % capacity(); + } + + /* 先頭エンキュー */ + public void pushFirst(int num) { + if (queSize == capacity()) { + System.out.println("両端キューが満杯です"); + return; + } + // 先頭ポインタを左に移動し、境界を越える場合は配列の末尾に回る + front = index(front - 1); + // 先頭に num を追加 + nums[front] = num; + queSize++; + } + + /* 末尾エンキュー */ + public void pushLast(int num) { + if (queSize == capacity()) { + System.out.println("両端キューが満杯です"); + return; + } + // 末尾ポインタを計算し、末尾に要素を追加 + int rear = index(front + queSize); + nums[rear] = num; + queSize++; + } + + /* 先頭デキュー */ + public int popFirst() { + int num = peekFirst(); + // 先頭ポインタを右に移動 + 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(); + // 末尾要素のインデックスを計算 + int last = index(front + queSize - 1); + 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)]; + } + return res; + } +} + +public class array_deque { + public static void main(String[] args) { + /* 両端キューを初期化 */ + int capacity = 10; + ArrayDeque deque = new ArrayDeque(capacity); + + /* 末尾エンキュー */ + deque.pushLast(3); + deque.pushLast(2); + deque.pushLast(5); + 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())); + int popLast = deque.popLast(); + System.out.println("末尾デキュー要素 = " + popLast + "、末尾デキュー後 deque = " + Arrays.toString(deque.toArray())); + + /* 両端キューの長さを取得 */ + int size = deque.size(); + System.out.println("両端キューの長さ size = " + size); + + /* 両端キューが空かどうかを判定 */ + boolean isEmpty = deque.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 new file mode 100644 index 000000000..420878b50 --- /dev/null +++ b/ja/codes/java/chapter_stack_and_queue/array_queue.java @@ -0,0 +1,114 @@ +/** + * File: array_queue.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* 配列に基づくキュークラス */ +class ArrayQueue { + private int[] nums; // 要素を格納する配列 + private int front; // キューヘッドポインタ、最初の要素を指す + private int queSize; // キューの長さ + + public ArrayQueue(int capacity) { + nums = new int[capacity]; + front = queSize = 0; + } + + /* キューの容量を取得 */ + public int capacity() { + return nums.length; + } + + /* キューの長さを取得 */ + public int size() { + return queSize; + } + + /* キューが空かどうかを判定 */ + public boolean isEmpty() { + return queSize == 0; + } + + /* エンキュー */ + public void push(int num) { + if (queSize == capacity()) { + System.out.println("キューが満杯です"); + return; + } + // リアポインタを計算:front + queSize + // モジュロ操作により rear が配列の長さを超えることを回避 + int rear = (front + queSize) % capacity(); + // 要素をキューリアに追加 + 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 IndexOutOfBoundsException(); + return nums[front]; + } + + /* 配列を返す */ + 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()]; + } + return res; + } +} + +public class array_queue { + public static void main(String[] args) { + /* キューを初期化 */ + int capacity = 10; + ArrayQueue queue = new ArrayQueue(capacity); + + /* 要素をエンキュー */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + 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())); + + /* キューの長さを取得 */ + int size = queue.size(); + System.out.println("キューの長さ size = " + size); + + /* 空かどうかを判定 */ + boolean isEmpty = queue.isEmpty(); + System.out.println("キューが空か = " + isEmpty); + + /* 連続エンキューのテスト */ + for (int i = 0; i < 10; i++) { + queue.push(i); + } + 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 new file mode 100644 index 000000000..07f573cec --- /dev/null +++ b/ja/codes/java/chapter_stack_and_queue/array_stack.java @@ -0,0 +1,84 @@ +/** + * File: array_stack.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* 配列に基づくスタッククラス */ +class ArrayStack { + private ArrayList stack; + + public ArrayStack() { + // リスト(動的配列)を初期化 + stack = new ArrayList<>(); + } + + /* スタックの長さを取得 */ + public int size() { + return stack.size(); + } + + /* スタックが空かどうかを判定 */ + public boolean isEmpty() { + return size() == 0; + } + + /* プッシュ */ + public void push(int num) { + stack.add(num); + } + + /* ポップ */ + public int pop() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return stack.remove(size() - 1); + } + + /* スタックトップ要素にアクセス */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return stack.get(size() - 1); + } + + /* List を Array に変換して返す */ + public Object[] toArray() { + return stack.toArray(); + } +} + +public class array_stack { + public static void main(String[] args) { + /* スタックを初期化 */ + ArrayStack stack = new ArrayStack(); + + /* 要素をプッシュ */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + 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())); + + /* スタックの長さを取得 */ + int size = stack.size(); + System.out.println("スタックの長さ size = " + size); + + /* 空かどうかを判定 */ + boolean isEmpty = stack.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 new file mode 100644 index 000000000..0c7b5cfc9 --- /dev/null +++ b/ja/codes/java/chapter_stack_and_queue/deque.java @@ -0,0 +1,46 @@ +/** + * File: deque.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +public class deque { + public static void main(String[] args) { + /* 両端キューを初期化 */ + Deque deque = new LinkedList<>(); + deque.offerLast(3); + deque.offerLast(2); + deque.offerLast(5); + 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); + deque.offerFirst(1); + System.out.println("要素1を先頭にエンキュー、deque = " + deque); + + /* 要素のデキュー */ + int popLast = deque.pollLast(); + System.out.println("両端キュー末尾要素 = " + popLast + "、末尾からデキュー後 " + deque); + int popFirst = deque.pollFirst(); + System.out.println("両端キュー先頭要素 = " + popFirst + "、先頭からデキュー後 " + deque); + + /* 両端キューの長さを取得 */ + int size = deque.size(); + System.out.println("両端キューの長さ size = " + size); + + /* 両端キューが空かどうかを判定 */ + boolean isEmpty = deque.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 new file mode 100644 index 000000000..f8861bbe7 --- /dev/null +++ b/ja/codes/java/chapter_stack_and_queue/linkedlist_deque.java @@ -0,0 +1,176 @@ +/** + * File: linkedlist_deque.java + * Created Time: 2023-01-20 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* 双方向連結リストノード */ +class ListNode { + int val; // ノード値 + ListNode next; // 後続ノードへの参照 + ListNode prev; // 前任ノードへの参照 + + ListNode(int val) { + this.val = val; + prev = next = null; + } +} + +/* 双方向連結リストに基づく両端キュークラス */ +class LinkedListDeque { + private ListNode front, rear; // 先頭ノード front、末尾ノード rear + private int queSize = 0; // 両端キューの長さ + + public LinkedListDeque() { + front = rear = null; + } + + /* 両端キューの長さを取得 */ + public int size() { + return queSize; + } + + /* 両端キューが空かどうかを判定 */ + public boolean isEmpty() { + return size() == 0; + } + + /* エンキュー操作 */ + private void push(int num, boolean isFront) { + ListNode node = new ListNode(num); + // リストが空の場合、front と rear の両方を node に指す + if (isEmpty()) + front = rear = node; + // 先頭エンキュー操作 + else if (isFront) { + // node をリストの先頭に追加 + front.prev = node; + node.next = front; + front = node; // front を更新 + // 末尾エンキュー操作 + } else { + // node をリストの末尾に追加 + rear.next = node; + node.prev = rear; + rear = node; // rear を更新 + } + queSize++; // 長さを更新 + } + + /* 先頭エンキュー */ + public void pushFirst(int num) { + push(num, true); + } + + /* 末尾エンキュー */ + public void pushLast(int num) { + push(num, false); + } + + /* デキュー操作 */ + private int pop(boolean isFront) { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + int val; + // 先頭デキュー操作 + if (isFront) { + val = front.val; // 一時的に先頭ノード値を保存 + // 次のノードを削除 + ListNode fNext = front.next; + if (fNext != null) { + fNext.prev = null; + front.next = null; + } + front = fNext; // front を更新 + // 末尾デキュー操作 + } else { + val = rear.val; // 一時的に末尾ノード値を保存 + // 前のノードを削除 + ListNode rPrev = rear.prev; + if (rPrev != null) { + rPrev.next = null; + rear.prev = null; + } + rear = rPrev; // rear を更新 + } + 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()]; + for (int i = 0; i < res.length; i++) { + res[i] = node.val; + node = node.next; + } + return res; + } +} + +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())); + + /* 先頭エンキュー */ + 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())); + int popLast = deque.popLast(); + System.out.println("末尾デキュー要素 = " + popLast + "、末尾デキュー後 deque = " + Arrays.toString(deque.toArray())); + + /* 両端キューの長さを取得 */ + int size = deque.size(); + System.out.println("両端キューの長さ size = " + size); + + /* 両端キューが空かどうかを判定 */ + boolean isEmpty = deque.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 new file mode 100644 index 000000000..fb3347fe0 --- /dev/null +++ b/ja/codes/java/chapter_stack_and_queue/linkedlist_queue.java @@ -0,0 +1,104 @@ +/** + * File: linkedlist_queue.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +/* 連結リストに基づくキュークラス */ +class LinkedListQueue { + private ListNode front, rear; // 先頭ノード front、末尾ノード rear + private int queSize = 0; + + public LinkedListQueue() { + front = null; + rear = null; + } + + /* キューの長さを取得 */ + public int size() { + return queSize; + } + + /* キューが空かどうかを判定 */ + public boolean isEmpty() { + return size() == 0; + } + + /* エンキュー */ + public void push(int num) { + // 末尾ノードの後ろに num を追加 + ListNode node = new ListNode(num); + // キューが空の場合、先頭と末尾ノードの両方をそのノードにポイント + if (front == null) { + front = node; + rear = node; + // キューが空でない場合、そのノードを末尾ノードの後ろに追加 + } else { + 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 IndexOutOfBoundsException(); + return front.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_queue { + public static void main(String[] args) { + /* キューを初期化 */ + LinkedListQueue queue = new LinkedListQueue(); + + /* 要素をエンキュー */ + queue.push(1); + queue.push(3); + queue.push(2); + queue.push(5); + 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())); + + /* キューの長さを取得 */ + int size = queue.size(); + System.out.println("キューの長さ size = " + size); + + /* キューが空かどうかを判定 */ + boolean isEmpty = queue.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 new file mode 100644 index 000000000..2587ee676 --- /dev/null +++ b/ja/codes/java/chapter_stack_and_queue/linkedlist_stack.java @@ -0,0 +1,95 @@ +/** + * File: linkedlist_stack.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; +import utils.*; + +/* 連結リストに基づくスタッククラス */ +class LinkedListStack { + private ListNode stackPeek; // ヘッドノードをスタックトップとして使用 + private int stkSize = 0; // スタックの長さ + + public LinkedListStack() { + stackPeek = null; + } + + /* スタックの長さを取得 */ + public int size() { + return stkSize; + } + + /* スタックが空かどうかを判定 */ + public boolean isEmpty() { + return size() == 0; + } + + /* プッシュ */ + public void push(int num) { + ListNode node = new ListNode(num); + node.next = stackPeek; + stackPeek = node; + stkSize++; + } + + /* ポップ */ + public int pop() { + int num = peek(); + stackPeek = stackPeek.next; + stkSize--; + return num; + } + + /* スタックトップ要素にアクセス */ + public int peek() { + if (isEmpty()) + throw new IndexOutOfBoundsException(); + return stackPeek.val; + } + + /* List を Array に変換して返す */ + public int[] toArray() { + 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 { + public static void main(String[] args) { + /* スタックを初期化 */ + LinkedListStack stack = new LinkedListStack(); + + /* 要素をプッシュ */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + 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())); + + /* スタックの長さを取得 */ + int size = stack.size(); + System.out.println("スタックの長さ size = " + size); + + /* 空かどうかを判定 */ + boolean isEmpty = stack.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 new file mode 100644 index 000000000..48383195c --- /dev/null +++ b/ja/codes/java/chapter_stack_and_queue/queue.java @@ -0,0 +1,40 @@ +/** + * File: queue.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +public class queue { + public static void main(String[] args) { + /* キューを初期化 */ + Queue queue = new LinkedList<>(); + + /* 要素をエンキュー */ + queue.offer(1); + queue.offer(3); + queue.offer(2); + queue.offer(5); + 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); + + /* キューの長さを取得 */ + int size = queue.size(); + System.out.println("キューの長さ size = " + size); + + /* キューが空かどうかを判定 */ + boolean isEmpty = queue.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 new file mode 100644 index 000000000..7793a0d5c --- /dev/null +++ b/ja/codes/java/chapter_stack_and_queue/stack.java @@ -0,0 +1,40 @@ +/** + * File: stack.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_stack_and_queue; + +import java.util.*; + +public class stack { + public static void main(String[] args) { + /* スタックを初期化 */ + Stack stack = new Stack<>(); + + /* 要素をプッシュ */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + 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); + + /* スタックの長さを取得 */ + int size = stack.size(); + System.out.println("スタックの長さ size = " + size); + + /* 空かどうかを判定 */ + boolean isEmpty = stack.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 new file mode 100644 index 000000000..80a075dc1 --- /dev/null +++ b/ja/codes/java/chapter_tree/array_binary_tree.java @@ -0,0 +1,136 @@ +/** + * File: array_binary_tree.java + * Created Time: 2023-07-19 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; +import java.util.*; + +/* 配列ベースの二分木クラス */ +class ArrayBinaryTree { + private List tree; + + /* コンストラクタ */ + public ArrayBinaryTree(List arr) { + tree = new ArrayList<>(arr); + } + + /* リストの容量 */ + public int size() { + return tree.size(); + } + + /* インデックス i のノードの値を取得 */ + public Integer val(int i) { + // インデックスが範囲外の場合、null を返す(空の位置を表す) + if (i < 0 || i >= size()) + return null; + return tree.get(i); + } + + /* インデックス i のノードの左の子のインデックスを取得 */ + public Integer left(int i) { + return 2 * i + 1; + } + + /* インデックス i のノードの右の子のインデックスを取得 */ + public Integer right(int i) { + return 2 * i + 2; + } + + /* インデックス i のノードの親のインデックスを取得 */ + public Integer parent(int i) { + return (i - 1) / 2; + } + + /* レベル順走査 */ + public List levelOrder() { + List res = new ArrayList<>(); + // 配列を走査 + for (int i = 0; i < size(); i++) { + if (val(i) != null) + res.add(val(i)); + } + 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); + // 中順走査 + if ("in".equals(order)) + res.add(val(i)); + dfs(right(i), order, res); + // 後順走査 + if ("post".equals(order)) + res.add(val(i)); + } + + /* 前順走査 */ + public List preOrder() { + List res = new ArrayList<>(); + dfs(0, "pre", res); + return res; + } + + /* 中順走査 */ + public List inOrder() { + List res = new ArrayList<>(); + dfs(0, "in", res); + return res; + } + + /* 後順走査 */ + public List postOrder() { + List res = new ArrayList<>(); + dfs(0, "post", res); + return res; + } +} + +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); + System.out.println("\n二分木を初期化\n"); + System.out.println("二分木の配列表現:"); + System.out.println(arr); + System.out.println("二分木の連結リスト表現:"); + PrintUtil.printTree(root); + + // 配列ベースの二分木クラス + ArrayBinaryTree abt = new ArrayBinaryTree(arr); + + // ノードにアクセス + int i = 1; + 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))); + + // 木を走査 + List res = abt.levelOrder(); + System.out.println("\nレベル順走査は:" + res); + res = abt.preOrder(); + System.out.println("前順走査は:" + res); + res = abt.inOrder(); + System.out.println("中順走査は:" + res); + res = abt.postOrder(); + 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 new file mode 100644 index 000000000..a7261b9af --- /dev/null +++ b/ja/codes/java/chapter_tree/avl_tree.java @@ -0,0 +1,220 @@ +/** + * File: avl_tree.java + * Created Time: 2022-12-10 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; + +/* AVL木 */ +class AVLTree { + TreeNode root; // 根ノード + + /* ノードの高さを取得 */ + public int height(TreeNode node) { + // 空ノードの高さは -1、葉ノードの高さは 0 + return node == null ? -1 : node.height; + } + + /* ノードの高さを更新 */ + private 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); + } + + /* 右回転操作 */ + private 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; + } + + /* 左回転操作 */ + private 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; + } + + /* 回転操作を実行して部分木の平衡を回復 */ + private TreeNode rotate(TreeNode 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; + } + + /* ノードを挿入 */ + public void insert(int val) { + root = insertHelper(root, val); + } + + /* 再帰的にノードを挿入(補助メソッド) */ + private 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); + } + + /* 再帰的にノードを削除(補助メソッド) */ + private 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 != null ? node.left : node.right; + // 子ノード数 = 0、ノードを削除して戻る + if (child == null) + return null; + // 子ノード数 = 1、ノードを削除 + 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; + } + + /* ノードを検索 */ + 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); + 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木は "); + PrintUtil.printTree(tree.root); + } + + public static void main(String[] args) { + /* 空のAVL木を初期化 */ + AVLTree 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 のノードを削除 + + /* ノードを検索 */ + TreeNode node = avlTree.search(7); + 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 new file mode 100644 index 000000000..f27245b4e --- /dev/null +++ b/ja/codes/java/chapter_tree/binary_search_tree.java @@ -0,0 +1,158 @@ +/** + * File: binary_search_tree.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; + +/* 二分探索木 */ +class BinarySearchTree { + private 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 TreeNode(num); + 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 または 1 + if (cur.left == null || cur.right == null) { + // 子ノード数 = 0/1 の場合、child = null/その子ノード + TreeNode child = cur.left != null ? 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); + // cur を tmp で置き換える + cur.val = tmp.val; + } + } +} + +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"); + PrintUtil.printTree(bst.getRoot()); + + /* ノードを検索 */ + TreeNode node = bst.search(7); + System.out.println("\n見つかったノードオブジェクトは " + node + "、ノードの値 = " + node.val); + + /* ノードを挿入 */ + bst.insert(16); + System.out.println("\nノード 16 を挿入後、二分木は\n"); + PrintUtil.printTree(bst.getRoot()); + + /* ノードを削除 */ + bst.remove(1); + System.out.println("\nノード 1 を削除後、二分木は\n"); + PrintUtil.printTree(bst.getRoot()); + bst.remove(2); + System.out.println("\nノード 2 を削除後、二分木は\n"); + PrintUtil.printTree(bst.getRoot()); + bst.remove(4); + 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 new file mode 100644 index 000000000..78e84ea27 --- /dev/null +++ b/ja/codes/java/chapter_tree/binary_tree.java @@ -0,0 +1,40 @@ +/** + * File: binary_tree.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; + +public class binary_tree { + public static void main(String[] args) { + /* 二分木を初期化 */ + // ノードを初期化 + 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; + n2.right = n5; + System.out.println("\n二分木を初期化\n"); + PrintUtil.printTree(n1); + + /* ノードの挿入と削除 */ + TreeNode P = new TreeNode(0); + // ノード P を n1 -> n2 の間に挿入 + n1.left = P; + P.left = n2; + System.out.println("\nノード P を挿入後\n"); + PrintUtil.printTree(n1); + // ノード P を削除 + n1.left = n2; + 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 new file mode 100644 index 000000000..50585b815 --- /dev/null +++ b/ja/codes/java/chapter_tree/binary_tree_bfs.java @@ -0,0 +1,42 @@ +/** + * File: binary_tree_bfs.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; +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); // ノードの値を保存 + if (node.left != null) + queue.offer(node.left); // 左の子ノードをエンキュー + if (node.right != null) + 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); + } +} \ 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 new file mode 100644 index 000000000..d35be32ee --- /dev/null +++ b/ja/codes/java/chapter_tree/binary_tree_dfs.java @@ -0,0 +1,68 @@ +/** + * File: binary_tree_dfs.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package chapter_tree; + +import utils.*; +import java.util.*; + +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); + } + + /* 中順走査 */ + static void inOrder(TreeNode root) { + if (root == null) + return; + // 訪問優先度: 左部分木 -> 根ノード -> 右部分木 + inOrder(root.left); + list.add(root.val); + inOrder(root.right); + } + + /* 後順走査 */ + static void postOrder(TreeNode root) { + if (root == null) + return; + // 訪問優先度: 左部分木 -> 右部分木 -> 根ノード + postOrder(root.left); + postOrder(root.right); + list.add(root.val); + } + + 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); + + /* 中順走査 */ + list.clear(); + inOrder(root); + System.out.println("\n中順走査でのノードの出力順序 = " + list); + + /* 後順走査 */ + list.clear(); + postOrder(root); + 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 new file mode 100644 index 000000000..8257ed35c --- /dev/null +++ b/ja/codes/java/utils/ListNode.java @@ -0,0 +1,28 @@ +/** + * File: ListNode.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package utils; + +/* 連結リストノード */ +public class ListNode { + public int val; + public ListNode next; + + public ListNode(int x) { + val = x; + } + + /* リストを連結リストにデシリアライズ */ + public static ListNode arrToLinkedList(int[] arr) { + ListNode dum = new ListNode(0); + ListNode head = dum; + for (int val : arr) { + head.next = new ListNode(val); + head = head.next; + } + 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 new file mode 100644 index 000000000..0d6ff1895 --- /dev/null +++ b/ja/codes/java/utils/PrintUtil.java @@ -0,0 +1,116 @@ +/** + * File: PrintUtil.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package utils; + +import java.util.*; + +class Trunk { + Trunk prev; + String str; + + Trunk(Trunk prev, String str) { + this.prev = prev; + this.str = str; + } +}; + +public class PrintUtil { + /* 行列を印刷 (配列) */ + public static void printMatrix(T[][] matrix) { + System.out.println("["); + for (T[] row : matrix) { + System.out.println(" " + row + ","); + } + System.out.println("]"); + } + + /* 行列を印刷 (リスト) */ + public static void printMatrix(List> matrix) { + System.out.println("["); + for (List row : matrix) { + System.out.println(" " + row + ","); + } + System.out.println("]"); + } + + /* 連結リストを印刷 */ + public static void printLinkedList(ListNode head) { + List list = new ArrayList<>(); + while (head != null) { + list.add(String.valueOf(head.val)); + head = head.next; + } + 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/ + */ + public static void printTree(TreeNode root, Trunk prev, boolean isRight) { + if (root == null) { + return; + } + + String prev_str = " "; + Trunk trunk = new 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); + System.out.println(" " + 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); + 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.println(list); + 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 new file mode 100644 index 000000000..b64df35ce --- /dev/null +++ b/ja/codes/java/utils/TreeNode.java @@ -0,0 +1,73 @@ +/** + * File: TreeNode.java + * Created Time: 2022-11-25 + * Author: krahets (krahets@163.com) + */ + +package utils; + +import java.util.*; + +/* 二分木ノードクラス */ +public class TreeNode { + public int val; // ノード値 + public int height; // ノード高さ + public TreeNode left; // 左子ノードへの参照 + public TreeNode right; // 右子ノードへの参照 + + /* コンストラクタ */ + public TreeNode(int x) { + 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 + // ——— 1 + // \——— 2 + // | /——— 9 + // \——— 4 + // \——— 8 + + /* リストを二分木にデシリアライズ:再帰的 */ + private static TreeNode listToTreeDFS(List arr, int i) { + if (i < 0 || i >= arr.size() || arr.get(i) == null) { + return null; + } + TreeNode root = new TreeNode(arr.get(i)); + root.left = listToTreeDFS(arr, 2 * i + 1); + root.right = listToTreeDFS(arr, 2 * i + 2); + 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; + while (i >= res.size()) { + res.add(null); + } + res.set(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 = 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 new file mode 100644 index 000000000..fbd941b3d --- /dev/null +++ b/ja/codes/java/utils/Vertex.java @@ -0,0 +1,36 @@ +/** + * File: Vertex.java + * Created Time: 2023-02-15 + * Author: krahets (krahets@163.com) + */ + +package utils; + +import java.util.*; + +/* 頂点クラス */ +public class Vertex { + public int val; + + public Vertex(int val) { + this.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 = new ArrayList<>(); + for (Vertex vet : vets) { + vals.add(vet.val); + } + return vals; + } +} \ No newline at end of file diff --git a/ja/codes/python/chapter_array_and_linkedlist/array.py b/ja/codes/python/chapter_array_and_linkedlist/array.py new file mode 100644 index 000000000..437324f8f --- /dev/null +++ b/ja/codes/python/chapter_array_and_linkedlist/array.py @@ -0,0 +1,100 @@ +""" +File: array.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + +import random + + +def random_access(nums: list[int]) -> int: + """要素へのランダムアクセス""" + # 区間 [0, len(nums)-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として扱う +def extend(nums: list[int], enlarge: int) -> list[int]: + """配列の長さを拡張""" + # 拡張された長さの配列を初期化 + res = [0] * (len(nums) + enlarge) + # 元の配列のすべての要素を新しい配列にコピー + for i in range(len(nums)): + res[i] = nums[i] + # 拡張後の新しい配列を返す + return res + + +def insert(nums: list[int], num: int, index: int): + """インデックス index に要素 num を挿入""" + # インデックス index より後のすべての要素を1つ後ろに移動 + for i in range(len(nums) - 1, index, -1): + nums[i] = nums[i - 1] + # num を index の位置の要素に代入 + nums[index] = num + + +def remove(nums: list[int], index: int): + """インデックス 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 + return -1 + + +"""Driver Code""" +if __name__ == "__main__": + # 配列を初期化 + arr = [0] * 5 + print("配列 arr =", arr) + nums = [1, 3, 2, 5, 4] + print("配列 nums =", nums) + + # ランダムアクセス + random_num: int = random_access(nums) + print("nums のランダムな要素を取得", random_num) + + # 長さの拡張 + nums: list[int] = 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) + + # 要素の検索 + index: int = find(nums, 3) + print("nums で要素 3 を検索、結果は index =", index) \ No newline at end of file diff --git a/ja/codes/python/chapter_array_and_linkedlist/linked_list.py b/ja/codes/python/chapter_array_and_linkedlist/linked_list.py new file mode 100644 index 000000000..82b44d851 --- /dev/null +++ b/ja/codes/python/chapter_array_and_linkedlist/linked_list.py @@ -0,0 +1,85 @@ +""" +File: linked_list.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode, print_linked_list + + +def insert(n0: ListNode, P: ListNode): + """連結リストのノード n0 の後にノード P を挿入""" + n1 = n0.next + P.next = n1 + n0.next = P + + +def remove(n0: ListNode): + """連結リストのノード n0 の後の最初のノードを削除""" + if not n0.next: + return + # n0 -> P -> n1 + P = n0.next + n1 = P.next + n0.next = n1 + + +def access(head: ListNode, index: int) -> ListNode | None: + """連結リストのインデックス index のノードにアクセス""" + for _ in range(index): + if not head: + return None + head = head.next + return head + + +def find(head: ListNode, target: int) -> int: + """連結リストで値 target を持つ最初のノードを検索""" + index = 0 + while head: + if head.val == target: + return index + head = head.next + index += 1 + return -1 + + +"""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_linked_list(n0) + + # ノードを挿入 + p = ListNode(0) + insert(n0, p) + print("ノード挿入後の連結リスト") + print_linked_list(n0) + + # ノードを削除 + remove(n0) + 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 diff --git a/ja/codes/python/chapter_array_and_linkedlist/list.py b/ja/codes/python/chapter_array_and_linkedlist/list.py new file mode 100644 index 000000000..af76de80f --- /dev/null +++ b/ja/codes/python/chapter_array_and_linkedlist/list.py @@ -0,0 +1,56 @@ +""" +File: list.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + +"""Driver Code""" +if __name__ == "__main__": + # リストを初期化 + nums: list[int] = [1, 3, 2, 5, 4] + print("\nリスト nums =", nums) + + # 要素にアクセス + x: int = nums[1] + print("\nインデックス 1 の要素にアクセス、結果は x =", x) + + # 要素を更新 + nums[1] = 0 + print("\nインデックス 1 の要素を 0 に更新、結果は nums =", nums) + + # リストをクリア + nums.clear() + print("\nリストをクリア後、nums =", nums) + + # 末尾に要素を追加 + nums.append(1) + nums.append(3) + nums.append(2) + nums.append(5) + nums.append(4) + print("\n要素を追加後、nums =", nums) + + # 中間に要素を挿入 + nums.insert(3, 6) + print("\nインデックス 3 に数値 6 を挿入、結果は nums =", nums) + + # 要素を削除 + nums.pop(3) + print("\nインデックス 3 の要素を削除、結果は nums =", nums) + + # インデックスによるリストの走査 + count = 0 + for i in range(len(nums)): + count += nums[i] + # リスト要素の走査 + for num in nums: + count += num + + # 2つのリストを連結 + nums1 = [6, 8, 7, 10, 9] + nums += nums1 + print("\nリスト nums1 を nums に連結、結果は nums =", nums) + + # リストをソート + nums.sort() + print("\nリストをソート後、nums =", nums) \ No newline at end of file diff --git a/ja/codes/python/chapter_array_and_linkedlist/my_list.py b/ja/codes/python/chapter_array_and_linkedlist/my_list.py new file mode 100644 index 000000000..bca475647 --- /dev/null +++ b/ja/codes/python/chapter_array_and_linkedlist/my_list.py @@ -0,0 +1,118 @@ +""" +File: my_list.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + + +class MyList: + """リストクラス""" + + def __init__(self): + """コンストラクタ""" + self._capacity: int = 10 # リストの容量 + self._arr: list[int] = [0] * self._capacity # 配列(リスト要素を格納) + self._size: int = 0 # リストの長さ(現在の要素数) + 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") + return self._arr[index] + + def set(self, num: int, index: int): + """要素を更新""" + if index < 0 or index >= self._size: + raise IndexError("Index out of bounds") + self._arr[index] = num + + def add(self, num: int): + """末尾に要素を追加""" + # 要素数が容量を超える場合、拡張メカニズムをトリガー + if self.size() == self.capacity(): + self.extend_capacity() + self._arr[self._size] = num + self._size += 1 + + def insert(self, num: int, index: int): + """中間に要素を挿入""" + if index < 0 or index >= self._size: + raise IndexError("Index out of bounds") + # 要素数が容量を超える場合、拡張メカニズムをトリガー + if self._size == self.capacity(): + self.extend_capacity() + # インデックス index より後のすべての要素を1つ後ろに移動 + for j in range(self._size - 1, index - 1, -1): + self._arr[j + 1] = self._arr[j] + self._arr[index] = num + # 要素数を更新 + self._size += 1 + + def remove(self, index: int) -> int: + """要素を削除""" + if index < 0 or index >= self._size: + raise IndexError("Index out of bounds") + num = self._arr[index] + # インデックス index より後のすべての要素を1つ前に移動 + for j in range(index, self._size - 1): + self._arr[j] = self._arr[j + 1] + # 要素数を更新 + self._size -= 1 + # 削除された要素を返す + return num + + def extend_capacity(self): + """リストを拡張""" + # 元の配列の _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] + + +"""Driver Code""" +if __name__ == "__main__": + # リストを初期化 + nums = MyList() + # 末尾に要素を追加 + nums.add(1) + nums.add(3) + nums.add(2) + nums.add(5) + nums.add(4) + print(f"リスト nums = {nums.to_array()} ,容量 = {nums.capacity()} ,長さ = {nums.size()}") + + # 中間に要素を挿入 + nums.insert(6, index=3) + print("インデックス 3 に数値 6 を挿入、結果は nums =", nums.to_array()) + + # 要素を削除 + nums.remove(3) + print("インデックス 3 の要素を削除、結果は nums =", nums.to_array()) + + # 要素にアクセス + num = nums.get(1) + print("インデックス 1 の要素にアクセス、結果は num =", num) + + # 要素を更新 + nums.set(0, 1) + print("インデックス 1 の要素を 0 に更新、結果は nums =", nums.to_array()) + + # 拡張メカニズムのテスト + for i in range(10): + # i = 5 のとき、リストの長さがリストの容量を超え、この時点で拡張メカニズムがトリガーされる + nums.add(i) + print(f"拡張後、リスト {nums.to_array()} ,容量 = {nums.capacity()} ,長さ = {nums.size()}") \ No newline at end of file diff --git a/ja/codes/python/chapter_backtracking/n_queens.py b/ja/codes/python/chapter_backtracking/n_queens.py new file mode 100644 index 000000000..8fa039dd8 --- /dev/null +++ b/ja/codes/python/chapter_backtracking/n_queens.py @@ -0,0 +1,62 @@ +""" +File: n_queens.py +Created Time: 2023-04-26 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + row: int, + n: int, + state: list[list[str]], + res: list[list[list[str]]], + cols: list[bool], + diags1: list[bool], + diags2: list[bool], +): + """バックトラッキングアルゴリズム: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' はクイーンを表し、'#' は空のスポットを表す + state = [["#" for _ in range(n)] for _ in range(n)] + cols = [False] * n # クイーンがある列を記録 + diags1 = [False] * (2 * n - 1) # クイーンがある主対角線を記録 + diags2 = [False] * (2 * n - 1) # クイーンがある副対角線を記録 + res = [] + backtrack(0, n, state, res, cols, diags1, diags2) + + return res + + +"""ドライバーコード""" +if __name__ == "__main__": + n = 4 + res = n_queens(n) + + print(f"チェスボードの寸法入力:{n}") + print(f"クイーン配置解の総数は {len(res)}") + for state in res: + print("--------------------") + for row in state: + print(row) \ No newline at end of file diff --git a/ja/codes/python/chapter_backtracking/permutations_i.py b/ja/codes/python/chapter_backtracking/permutations_i.py new file mode 100644 index 000000000..6b7abf08d --- /dev/null +++ b/ja/codes/python/chapter_backtracking/permutations_i.py @@ -0,0 +1,44 @@ +""" +File: permutations_i.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] +): + """バックトラッキングアルゴリズム:順列 I""" + # 状態の長さが要素数と等しいとき、解を記録 + if len(state) == len(choices): + res.append(list(state)) + return + # すべての選択肢を走査 + 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""" + res = [] + backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) + return res + + +"""ドライバーコード""" +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 diff --git a/ja/codes/python/chapter_backtracking/permutations_ii.py b/ja/codes/python/chapter_backtracking/permutations_ii.py new file mode 100644 index 000000000..4f758f57c --- /dev/null +++ b/ja/codes/python/chapter_backtracking/permutations_ii.py @@ -0,0 +1,46 @@ +""" +File: permutations_ii.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] +): + """バックトラッキングアルゴリズム:順列 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) # 選択された要素値を記録 + 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""" + res = [] + backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) + return res + + +"""ドライバーコード""" +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 diff --git a/ja/codes/python/chapter_backtracking/preorder_traversal_i_compact.py b/ja/codes/python/chapter_backtracking/preorder_traversal_i_compact.py new file mode 100644 index 000000000..a9316158e --- /dev/null +++ b/ja/codes/python/chapter_backtracking/preorder_traversal_i_compact.py @@ -0,0 +1,36 @@ +""" +File: preorder_traversal_i_compact.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree, list_to_tree + + +def pre_order(root: TreeNode): + """前順走査:例一""" + if root is None: + return + if root.val == 7: + # 解を記録 + res.append(root) + pre_order(root.left) + pre_order(root.right) + + +"""ドライバーコード""" +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 diff --git a/ja/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py b/ja/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py new file mode 100644 index 000000000..fc8af7640 --- /dev/null +++ b/ja/codes/python/chapter_backtracking/preorder_traversal_ii_compact.py @@ -0,0 +1,42 @@ +""" +File: preorder_traversal_ii_compact.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree, list_to_tree + + +def pre_order(root: TreeNode): + """前順走査:例二""" + 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() + + +"""ドライバーコード""" +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 へのすべてのパスを出力") + for path in res: + print([node.val for node in path]) \ No newline at end of file diff --git a/ja/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py b/ja/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py new file mode 100644 index 000000000..f92a30e98 --- /dev/null +++ b/ja/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py @@ -0,0 +1,43 @@ +""" +File: preorder_traversal_iii_compact.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree, list_to_tree + + +def pre_order(root: TreeNode): + """前順走査:例三""" + # 枝刈り + 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() + + +"""ドライバーコード""" +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 のノードは含まない") + for path in res: + print([node.val for node in path]) \ No newline at end of file diff --git a/ja/codes/python/chapter_backtracking/preorder_traversal_iii_template.py b/ja/codes/python/chapter_backtracking/preorder_traversal_iii_template.py new file mode 100644 index 000000000..a3e157e0b --- /dev/null +++ b/ja/codes/python/chapter_backtracking/preorder_traversal_iii_template.py @@ -0,0 +1,71 @@ +""" +File: preorder_traversal_iii_template.py +Created Time: 2023-04-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree, list_to_tree + + +def is_solution(state: list[TreeNode]) -> bool: + """現在の状態が解かどうかを判定""" + return state and state[-1].val == 7 + + +def record_solution(state: list[TreeNode], res: list[list[TreeNode]]): + """解を記録""" + res.append(list(state)) + + +def is_valid(state: list[TreeNode], choice: TreeNode) -> bool: + """現在の状態下で選択が合法かどうかを判定""" + return choice is not None and choice.val != 3 + + +def make_choice(state: list[TreeNode], choice: TreeNode): + """状態を更新""" + state.append(choice) + + +def undo_choice(state: list[TreeNode], choice: TreeNode): + """状態を復元""" + state.pop() + + +def backtrack( + state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]] +): + """バックトラッキングアルゴリズム:例三""" + # 解かどうかをチェック + 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) + + +"""ドライバーコード""" +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 のノードを含まないことを要求") + for path in res: + print([node.val for node in path]) \ No newline at end of file diff --git a/ja/codes/python/chapter_backtracking/subset_sum_i.py b/ja/codes/python/chapter_backtracking/subset_sum_i.py new file mode 100644 index 000000000..4683d3bc7 --- /dev/null +++ b/ja/codes/python/chapter_backtracking/subset_sum_i.py @@ -0,0 +1,48 @@ +""" +File: subset_sum_i.py +Created Time: 2023-06-17 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], target: int, choices: list[int], start: int, res: list[list[int]] +): + """バックトラッキングアルゴリズム:部分集合の和 I""" + # 部分集合の和が target と等しいとき、解を記録 + if target == 0: + res.append(list(state)) + return + # すべての選択肢を走査 + # 枝刈り二:start から走査を開始して重複する部分集合の生成を避ける + for i in range(start, len(choices)): + # 枝刈り一:部分集合の和が target を超える場合、直ちにループを終了 + # これは配列がソートされており、後の要素がより大きいため、部分集合の和は必ず target を超えるため + if target - choices[i] < 0: + break + # 試行:選択を行い、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 を解く""" + state = [] # 状態(部分集合) + nums.sort() # nums をソート + start = 0 # 走査の開始点 + res = [] # 結果リスト(部分集合リスト) + backtrack(state, target, nums, start, res) + return res + + +"""ドライバーコード""" +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 diff --git a/ja/codes/python/chapter_backtracking/subset_sum_i_naive.py b/ja/codes/python/chapter_backtracking/subset_sum_i_naive.py new file mode 100644 index 000000000..380867433 --- /dev/null +++ b/ja/codes/python/chapter_backtracking/subset_sum_i_naive.py @@ -0,0 +1,50 @@ +""" +File: subset_sum_i_naive.py +Created Time: 2023-06-17 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], + target: int, + total: int, + choices: list[int], + res: list[list[int]], +): + """バックトラッキングアルゴリズム:部分集合の和 I""" + # 部分集合の和が target と等しいとき、解を記録 + if total == target: + res.append(list(state)) + return + # すべての選択肢を走査 + for i in range(len(choices)): + # 枝刈り:部分集合の和が target を超える場合、その選択をスキップ + if total + choices[i] > target: + continue + # 試行:選択を行い、要素と 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 を解く(重複する部分集合を含む)""" + state = [] # 状態(部分集合) + total = 0 # 部分集合の和 + res = [] # 結果リスト(部分集合リスト) + backtrack(state, target, total, nums, res) + return res + + +"""ドライバーコード""" +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 diff --git a/ja/codes/python/chapter_backtracking/subset_sum_ii.py b/ja/codes/python/chapter_backtracking/subset_sum_ii.py new file mode 100644 index 000000000..aeac0fc94 --- /dev/null +++ b/ja/codes/python/chapter_backtracking/subset_sum_ii.py @@ -0,0 +1,52 @@ +""" +File: subset_sum_ii.py +Created Time: 2023-06-17 +Author: krahets (krahets@163.com) +""" + + +def backtrack( + state: list[int], target: int, choices: list[int], start: int, res: list[list[int]] +): + """バックトラッキングアルゴリズム:部分集合の和 II""" + # 部分集合の和が target と等しいとき、解を記録 + if target == 0: + res.append(list(state)) + return + # すべての選択肢を走査 + # 枝刈り二:start から走査を開始して重複する部分集合の生成を避ける + # 枝刈り三:start から走査を開始して同じ要素の重複選択を避ける + for i in range(start, len(choices)): + # 枝刈り一:部分集合の和が target を超える場合、直ちにループを終了 + # これは配列がソートされており、後の要素がより大きいため、部分集合の和は必ず target を超えるため + if target - choices[i] < 0: + break + # 枝刈り四:要素が左の要素と等しい場合、検索分岐が重複していることを示すため、スキップ + if i > start and choices[i] == choices[i - 1]: + continue + # 試行:選択を行い、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 を解く""" + state = [] # 状態(部分集合) + nums.sort() # nums をソート + start = 0 # 走査の開始点 + res = [] # 結果リスト(部分集合リスト) + backtrack(state, target, nums, start, res) + return res + + +"""ドライバーコード""" +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 diff --git a/ja/codes/python/chapter_computational_complexity/iteration.py b/ja/codes/python/chapter_computational_complexity/iteration.py new file mode 100644 index 000000000..b453664e3 --- /dev/null +++ b/ja/codes/python/chapter_computational_complexity/iteration.py @@ -0,0 +1,65 @@ +""" +File: iteration.py +Created Time: 2023-08-24 +Author: krahets (krahets@163.com) +""" + + +def for_loop(n: int) -> int: + """forループ""" + res = 0 + # 1, 2, ..., n-1, n の合計をループ + for i in range(1, n + 1): + res += i + return res + + +def while_loop(n: int) -> int: + """whileループ""" + res = 0 + i = 1 # 条件変数を初期化 + # 1, 2, ..., n-1, n の合計をループ + while i <= n: + res += i + i += 1 # 条件変数を更新 + return res + + +def while_loop_ii(n: int) -> int: + """whileループ(2つの更新)""" + res = 0 + i = 1 # 条件変数を初期化 + # 1, 4, 10, ... の合計をループ + while i <= n: + res += i + # 条件変数を更新 + i += 1 + i *= 2 + return res + + +def nested_for_loop(n: int) -> str: + """二重forループ""" + res = "" + # i = 1, 2, ..., n-1, n をループ + for i in range(1, n + 1): + # j = 1, 2, ..., n-1, n をループ + for j in range(1, n + 1): + res += f"({i}, {j}), " + return res + + +"""Driver Code""" +if __name__ == "__main__": + n = 5 + res = for_loop(n) + print(f"\nforループの合計結果 res = {res}") + + res = while_loop(n) + print(f"\nwhileループの合計結果 res = {res}") + + res = while_loop_ii(n) + print(f"\nwhileループ(2つの更新)の合計結果 res = {res}") + + res = nested_for_loop(n) + print(f"\n二重forループの走査結果 {res}") \ No newline at end of file diff --git a/ja/codes/python/chapter_computational_complexity/recursion.py b/ja/codes/python/chapter_computational_complexity/recursion.py new file mode 100644 index 000000000..e5b891536 --- /dev/null +++ b/ja/codes/python/chapter_computational_complexity/recursion.py @@ -0,0 +1,69 @@ +""" +File: recursion.py +Created Time: 2023-08-24 +Author: krahets (krahets@163.com) +""" + + +def recur(n: int) -> int: + """再帰""" + # 終了条件 + if n == 1: + 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 + + +def tail_recur(n, res): + """末尾再帰""" + # 終了条件 + if n == 0: + return res + # 末尾再帰呼び出し + return tail_recur(n - 1, res + n) + + +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) + res = fib(n - 1) + fib(n - 2) + # 結果 f(n) を返す + return res + + +"""Driver Code""" +if __name__ == "__main__": + n = 5 + res = recur(n) + print(f"\n再帰関数の合計結果 res = {res}") + + res = for_loop_recur(n) + 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 diff --git a/ja/codes/python/chapter_computational_complexity/space_complexity.py b/ja/codes/python/chapter_computational_complexity/space_complexity.py new file mode 100644 index 000000000..d79aac800 --- /dev/null +++ b/ja/codes/python/chapter_computational_complexity/space_complexity.py @@ -0,0 +1,90 @@ +""" +File: space_complexity.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode, TreeNode, print_tree + + +def function() -> int: + """関数""" + # 何らかの操作を実行 + return 0 + + +def constant(n: int): + """定数複雑度""" + # 定数、変数、オブジェクトは O(1) のスペースを占有 + a = 0 + nums = [0] * 10000 + node = ListNode(0) + # ループ内の変数は O(1) のスペースを占有 + for _ in range(n): + c = 0 + # ループ内の関数は O(1) のスペースを占有 + for _ in range(n): + function() + + +def linear(n: int): + """線形複雑度""" + # 長さ n のリストは O(n) のスペースを占有 + nums = [0] * 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 + linear_recur(n - 1) + + +def quadratic(n: int): + """平方複雑度""" + # 二次元リストは O(n^2) のスペースを占有 + num_matrix = [[0] * n for _ in range(n)] + + +def quadratic_recur(n: int) -> int: + """平方複雑度(再帰実装)""" + if n <= 0: + return 0 + 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) + root.left = build_tree(n - 1) + root.right = build_tree(n - 1) + return root + + +"""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 new file mode 100644 index 000000000..19c607da6 --- /dev/null +++ b/ja/codes/python/chapter_computational_complexity/time_complexity.py @@ -0,0 +1,151 @@ +""" +File: time_complexity.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + + +def constant(n: int) -> int: + """定数複雑度""" + count = 0 + size = 100000 + for _ in range(size): + count += 1 + return count + + +def linear(n: int) -> int: + """線形複雑度""" + count = 0 + for _ in range(n): + count += 1 + return count + + +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の二乗に比例する + for i in range(n): + for j in range(n): + count += 1 + return count + + +def bubble_sort(nums: list[int]) -> int: + """二次複雑度(バブルソート)""" + count = 0 # カウンタ + # 外側のループ: 未ソート範囲は [0, i] + for i in range(len(nums) - 1, 0, -1): + # 内側のループ: 未ソート範囲 [0, i] の最大要素を右端にスワップ + for j in range(i): + if 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つの個別操作を含む + return count + + +def exponential(n: int) -> int: + """指数複雑度(ループ実装)""" + count = 0 + base = 1 + # セルは毎回2つに分裂し、1, 2, 4, 8, ..., 2^(n-1) の数列を形成する + for _ in range(n): + for _ in range(base): + count += 1 + base *= 2 + # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count + + +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 + count += 1 + return count + + +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) + for _ in range(n): + count += 1 + return count + + +def factorial_recur(n: int) -> int: + """階乗複雑度(再帰実装)""" + if n == 0: + return 1 + count = 0 + # 1つからnに分岐 + for _ in range(n): + count += factorial_recur(n - 1) + return count + + +"""ドライバコード""" +if __name__ == "__main__": + # nを変更して、様々な複雑度での操作回数の変化傾向を体験できる + n = 8 + print("入力データサイズ n =", n) + + count: int = constant(n) + print("定数複雑度の操作回数 =", count) + + count: int = linear(n) + print("線形複雑度の操作回数 =", count) + count: int = array_traversal([0] * n) + print("線形複雑度(配列の走査)の操作回数 =", count) + + count: int = 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: int = exponential(n) + print("指数複雑度(ループ実装)の操作回数 =", count) + count: int = exp_recur(n) + print("指数複雑度(再帰実装)の操作回数 =", count) + + count: int = logarithmic(n) + print("対数複雑度(ループ実装)の操作回数 =", count) + count: int = log_recur(n) + print("対数複雑度(再帰実装)の操作回数 =", count) + + count: int = linear_log_recur(n) + print("線形対数複雑度(再帰実装)の操作回数 =", count) + + count: int = factorial_recur(n) + print("階乗複雑度(再帰実装)の操作回数 =", count) \ No newline at end of file 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 new file mode 100644 index 000000000..aa4decced --- /dev/null +++ b/ja/codes/python/chapter_computational_complexity/worst_best_time_complexity.py @@ -0,0 +1,36 @@ +""" +File: worst_best_time_complexity.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + +import random + + +def random_numbers(n: int) -> list[int]: + """要素 1, 2, ..., n を含む配列を生成、順序はシャッフル""" + # 配列 nums = 1, 2, 3, ..., n を生成 + nums = [i for i in range(1, n + 1)] + # 配列要素をランダムにシャッフル + random.shuffle(nums) + return nums + + +def find_one(nums: list[int]) -> int: + """配列 nums で数値 1 のインデックスを検索""" + for i in range(len(nums)): + # 要素 1 が配列の最初にある場合、最良時間計算量 O(1) を達成 + # 要素 1 が配列の最後にある場合、最悪時間計算量 O(n) を達成 + if nums[i] == 1: + return i + return -1 + + +"""Driver Code""" +if __name__ == "__main__": + for i in range(10): + 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 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 new file mode 100644 index 000000000..5e2f4dcaf --- /dev/null +++ b/ja/codes/python/chapter_divide_and_conquer/binary_search_recur.py @@ -0,0 +1,40 @@ +""" +File: binary_search_recur.py +Created Time: 2023-07-17 +Author: krahets (krahets@163.com) +""" + + +def dfs(nums: list[int], target: int, i: int, j: int) -> int: + """二分探索:問題 f(i, j)""" + # 区間が空の場合、対象要素がないことを示すため、-1 を返す + if i > j: + return -1 + # 中点インデックス m を計算 + m = (i + j) // 2 + if nums[m] < target: + # 再帰部分問題 f(m+1, j) + return dfs(nums, target, m + 1, j) + elif nums[m] > target: + # 再帰部分問題 f(i, m-1) + return dfs(nums, target, i, m - 1) + else: + # 対象要素を発見したため、そのインデックスを返す + return m + + +def binary_search(nums: list[int], target: int) -> int: + """二分探索""" + n = len(nums) + # 問題 f(0, n-1) を解く + return dfs(nums, target, 0, n - 1) + + +"""ドライバーコード""" +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 diff --git a/ja/codes/python/chapter_divide_and_conquer/build_tree.py b/ja/codes/python/chapter_divide_and_conquer/build_tree.py new file mode 100644 index 000000000..bc16b6eb6 --- /dev/null +++ b/ja/codes/python/chapter_divide_and_conquer/build_tree.py @@ -0,0 +1,54 @@ +""" +File: build_tree.py +Created Time: 2023-07-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree + + +def dfs( + preorder: list[int], + inorder_map: dict[int, int], + i: int, + l: int, + r: int, +) -> TreeNode | None: + """二分木の構築:分割統治""" + # 部分木の区間が空のとき終了 + if r - l < 0: + return None + # ルートノードを初期化 + root = TreeNode(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) + # ルートノードを返す + return root + + +def build_tree(preorder: list[int], inorder: list[int]) -> TreeNode | None: + """二分木を構築""" + # ハッシュテーブルを初期化、中順走査の要素からインデックスへのマッピングを保存 + inorder_map = {val: i for i, val in enumerate(inorder)} + root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1) + return root + + +"""ドライバーコード""" +if __name__ == "__main__": + preorder = [3, 9, 2, 1, 7] + inorder = [9, 3, 1, 2, 7] + print(f"前順走査 = {preorder}") + print(f"中順走査 = {inorder}") + + root = build_tree(preorder, inorder) + print("構築された二分木は:") + print_tree(root) \ No newline at end of file diff --git a/ja/codes/python/chapter_divide_and_conquer/hanota.py b/ja/codes/python/chapter_divide_and_conquer/hanota.py new file mode 100644 index 000000000..01a9f50e6 --- /dev/null +++ b/ja/codes/python/chapter_divide_and_conquer/hanota.py @@ -0,0 +1,53 @@ +""" +File: hanota.py +Created Time: 2023-07-16 +Author: krahets (krahets@163.com) +""" + + +def move(src: list[int], tar: list[int]): + """円盤を移動""" + # src の上から円盤を取り出す + 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 に移動 + if i == 1: + move(src, tar) + return + # 部分問題 f(i-1):tar の助けを借りて src の上の i-1 個の円盤を buf に移動 + dfs(i - 1, src, tar, buf) + # 部分問題 f(1):残りの 1 個の円盤を src から tar に移動 + move(src, tar) + # 部分問題 f(i-1):src の助けを借りて buf の上の i-1 個の円盤を 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 に移動 + dfs(n, A, B, C) + + +"""ドライバーコード""" +if __name__ == "__main__": + # リストの末尾が柱の上部 + A = [5, 4, 3, 2, 1] + B = [] + C = [] + print("初期状態:") + print(f"A = {A}") + print(f"B = {B}") + print(f"C = {C}") + + solve_hanota(A, B, C) + + print("円盤移動後:") + print(f"A = {A}") + print(f"B = {B}") + print(f"C = {C}") \ No newline at end of file diff --git a/ja/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py b/ja/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py new file mode 100644 index 000000000..d75f1916d --- /dev/null +++ b/ja/codes/python/chapter_dynamic_programming/climbing_stairs_backtrack.py @@ -0,0 +1,37 @@ +""" +File: climbing_stairs_backtrack.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def backtrack(choices: list[int], state: int, n: int, res: list[int]) -> int: + """バックトラッキング""" + # n 段目に登ったとき、解の数に 1 を加える + if state == n: + res[0] += 1 + # すべての選択肢を走査 + for choice in choices: + # 枝刈り: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] を使用して解の数を記録 + backtrack(choices, state, n, res) + return res[0] + + +"""ドライバーコード""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_backtrack(n) + print(f"{n} 段登り、合計 {res} 通りの解がある") \ No newline at end of file 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 new file mode 100644 index 000000000..871a4c53e --- /dev/null +++ b/ja/codes/python/chapter_dynamic_programming/climbing_stairs_constraint_dp.py @@ -0,0 +1,29 @@ +""" +File: climbing_stairs_constraint_dp.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def climbing_stairs_constraint_dp(n: int) -> int: + """制約付き階段登り:動的プログラミング""" + if n == 1 or n == 2: + return 1 + # 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] + + +"""ドライバーコード""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_constraint_dp(n) + print(f"{n} 段登り、合計 {res} 通りの解がある") \ No newline at end of file diff --git a/ja/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py b/ja/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py new file mode 100644 index 000000000..5c28af1c0 --- /dev/null +++ b/ja/codes/python/chapter_dynamic_programming/climbing_stairs_dfs.py @@ -0,0 +1,28 @@ +""" +File: climbing_stairs_dfs.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def dfs(i: int) -> int: + """探索""" + # 既知の dp[1] と dp[2] は、それらを返す + if i == 1 or i == 2: + return i + # dp[i] = dp[i-1] + dp[i-2] + count = dfs(i - 1) + dfs(i - 2) + return count + + +def climbing_stairs_dfs(n: int) -> int: + """階段登り:探索""" + return dfs(n) + + +"""ドライバーコード""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_dfs(n) + print(f"{n} 段登り、合計 {res} 通りの解がある") \ No newline at end of file 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 new file mode 100644 index 000000000..2fdc0b41f --- /dev/null +++ b/ja/codes/python/chapter_dynamic_programming/climbing_stairs_dfs_mem.py @@ -0,0 +1,35 @@ +""" +File: climbing_stairs_dfs_mem.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def dfs(i: int, mem: list[int]) -> int: + """記憶化探索""" + # 既知の dp[1] と dp[2] は、それらを返す + if i == 1 or i == 2: + return 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] を記録 + mem[i] = count + return count + + +def climbing_stairs_dfs_mem(n: int) -> int: + """階段登り:記憶化探索""" + # mem[i] は i 段目に登る解の総数を記録、-1 は記録なしを意味する + mem = [-1] * (n + 1) + return dfs(n, mem) + + +"""ドライバーコード""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_dfs_mem(n) + print(f"{n} 段登り、合計 {res} 通りの解がある") \ No newline at end of file diff --git a/ja/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py b/ja/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py new file mode 100644 index 000000000..e7f0797f5 --- /dev/null +++ b/ja/codes/python/chapter_dynamic_programming/climbing_stairs_dp.py @@ -0,0 +1,40 @@ +""" +File: climbing_stairs_dp.py +Created Time: 2023-06-30 +Author: krahets (krahets@163.com) +""" + + +def climbing_stairs_dp(n: int) -> int: + """階段登り:動的プログラミング""" + if n == 1 or n == 2: + return n + # 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 + for _ in range(3, n + 1): + a, b = b, a + b + return b + + +"""ドライバーコード""" +if __name__ == "__main__": + n = 9 + + res = climbing_stairs_dp(n) + print(f"{n} 段登り、合計 {res} 通りの解がある") + + res = climbing_stairs_dp_comp(n) + print(f"{n} 段登り、合計 {res} 通りの解がある") \ No newline at end of file diff --git a/ja/codes/python/chapter_dynamic_programming/coin_change.py b/ja/codes/python/chapter_dynamic_programming/coin_change.py new file mode 100644 index 000000000..9f69c82e7 --- /dev/null +++ b/ja/codes/python/chapter_dynamic_programming/coin_change.py @@ -0,0 +1,60 @@ +""" +File: coin_change.py +Created Time: 2023-07-10 +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 を選択しない + 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] 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 テーブルを初期化 + dp = [MAX] * (amt + 1) + dp[0] = 0 + # 状態遷移 + for i in range(1, n + 1): + # 順序で走査 + for a in range(1, amt + 1): + 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] if dp[amt] != MAX else -1 + + +"""ドライバーコード""" +if __name__ == "__main__": + coins = [1, 2, 5] + amt = 4 + + # 動的プログラミング + res = coin_change_dp(coins, amt) + print(f"目標金額に到達するのに必要な硬貨の最小数 = {res}") + + # 空間最適化動的プログラミング + res = coin_change_dp_comp(coins, amt) + print(f"目標金額に到達するのに必要な硬貨の最小数 = {res}") \ No newline at end of file diff --git a/ja/codes/python/chapter_dynamic_programming/coin_change_ii.py b/ja/codes/python/chapter_dynamic_programming/coin_change_ii.py new file mode 100644 index 000000000..2f7866e76 --- /dev/null +++ b/ja/codes/python/chapter_dynamic_programming/coin_change_ii.py @@ -0,0 +1,58 @@ +""" +File: coin_change_ii.py +Created Time: 2023-07-10 +Author: krahets (krahets@163.com) +""" + + +def coin_change_ii_dp(coins: list[int], amt: int) -> int: + """硬貨交換 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 を選択しない + 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] + + +def coin_change_ii_dp_comp(coins: list[int], amt: int) -> int: + """硬貨交換 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 を選択しない + dp[a] = dp[a] + else: + # 硬貨 i を選択しないのと選択するのとの両方の選択肢の和 + dp[a] = dp[a] + dp[a - coins[i - 1]] + return dp[amt] + + +"""ドライバーコード""" +if __name__ == "__main__": + coins = [1, 2, 5] + amt = 5 + + # 動的プログラミング + res = coin_change_ii_dp(coins, amt) + print(f"目標金額を構成する硬貨の組み合わせ数は {res}") + + # 空間最適化動的プログラミング + res = coin_change_ii_dp_comp(coins, amt) + print(f"目標金額を構成する硬貨の組み合わせ数は {res}") \ No newline at end of file diff --git a/ja/codes/python/chapter_dynamic_programming/edit_distance.py b/ja/codes/python/chapter_dynamic_programming/edit_distance.py new file mode 100644 index 000000000..964d8d06c --- /dev/null +++ b/ja/codes/python/chapter_dynamic_programming/edit_distance.py @@ -0,0 +1,123 @@ +""" +File: edit_distance.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 を返す + if i == 0 and 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 edit_distance_dfs(s, t, i - 1, 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) + # 最小編集数を返す + 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 を返す + if i == 0 and 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 edit_distance_dfs_mem(s, t, mem, i - 1, 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] = 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 つの文字をスキップ + dp[i][j] = dp[i - 1][j - 1] + else: + # 最小編集数 = 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] を一時的に保存 + dp[0] += 1 + # 状態遷移:残りの列 + for j in range(1, m + 1): + temp = dp[j] + if s[i - 1] == t[j - 1]: + # 2 つの文字が等しい場合、これら 2 つの文字をスキップ + dp[j] = leftup + else: + # 最小編集数 = 3 つの操作(挿入、削除、置換)からの最小編集数 + 1 + dp[j] = min(dp[j - 1], dp[j], leftup) + 1 + leftup = temp # 次の dp[i-1, j-1] のために更新 + return dp[m] + + +"""ドライバーコード""" +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}") + + # 記憶化探索 + mem = [[-1] * (m + 1) for _ in range(n + 1)] + res = edit_distance_dfs_mem(s, t, mem, n, m) + print(f"{s} を {t} に変更するために必要な最小編集数は {res}") + + # 動的プログラミング + res = edit_distance_dp(s, t) + print(f"{s} を {t} に変更するために必要な最小編集数は {res}") + + # 空間最適化動的プログラミング + res = edit_distance_dp_comp(s, t) + print(f"{s} を {t} に変更するために必要な最小編集数は {res}") \ No newline at end of file diff --git a/ja/codes/python/chapter_dynamic_programming/knapsack.py b/ja/codes/python/chapter_dynamic_programming/knapsack.py new file mode 100644 index 000000000..82ab6f3ef --- /dev/null +++ b/ja/codes/python/chapter_dynamic_programming/knapsack.py @@ -0,0 +1,101 @@ +""" +File: knapsack.py +Created Time: 2023-07-03 +Author: krahets (krahets@163.com) +""" + + +def knapsack_dfs(wgt: list[int], val: list[int], i: int, c: int) -> int: + """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 を入れないのと入れるのとの最大値を計算 + no = knapsack_dfs(wgt, val, i - 1, c) + yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] + # 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 を返す + 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 を入れないのと入れるのとの最大値を計算 + 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] = max(no, yes) + return mem[i][c] + + +def knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: + """0-1 ナップサック:動的プログラミング""" + n = len(wgt) + # dp テーブルを初期化 + dp = [[0] * (cap + 1) for _ in range(n + 1)] + # 状態遷移 + for i in range(1, n + 1): + for c in range(1, cap + 1): + 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] + + +def knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int: + """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 を選択しない + dp[c] = dp[c] + else: + # アイテム i を選択しないのと選択するのとで大きい値 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + return dp[cap] + + +"""ドライバーコード""" +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}") + + # 記憶化探索 + mem = [[-1] * (cap + 1) for _ in range(n + 1)] + res = knapsack_dfs_mem(wgt, val, mem, n, cap) + print(f"ナップサック容量を超えないアイテムの最大値は {res}") + + # 動的プログラミング + res = knapsack_dp(wgt, val, cap) + print(f"ナップサック容量を超えないアイテムの最大値は {res}") + + # 空間最適化動的プログラミング + res = knapsack_dp_comp(wgt, val, cap) + print(f"ナップサック容量を超えないアイテムの最大値は {res}") \ No newline at end of file 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 new file mode 100644 index 000000000..2fdf731cd --- /dev/null +++ b/ja/codes/python/chapter_dynamic_programming/min_cost_climbing_stairs_dp.py @@ -0,0 +1,43 @@ +""" +File: min_cost_climbing_stairs_dp.py +Created Time: 2023-06-30 +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 = [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] + a, b = cost[1], cost[2] + for i in range(3, n + 1): + a, b = b, min(a, b) + cost[i] + return b + + +"""ドライバーコード""" +if __name__ == "__main__": + cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] + print(f"階段コストリストの入力:{cost}") + + res = min_cost_climbing_stairs_dp(cost) + print(f"階段を登る最小コスト {res}") + + res = min_cost_climbing_stairs_dp_comp(cost) + print(f"階段を登る最小コスト {res}") \ No newline at end of file diff --git a/ja/codes/python/chapter_dynamic_programming/min_path_sum.py b/ja/codes/python/chapter_dynamic_programming/min_path_sum.py new file mode 100644 index 000000000..3e74327d9 --- /dev/null +++ b/ja/codes/python/chapter_dynamic_programming/min_path_sum.py @@ -0,0 +1,104 @@ +""" +File: min_path_sum.py +Created Time: 2023-07-04 +Author: krahets (krahets@163.com) +""" + +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) への最小パスコストを計算 + up = min_path_sum_dfs(grid, i - 1, j) + left = min_path_sum_dfs(grid, i, j - 1) + # 左上から (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) への最小パスコストを記録して返す + 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] + return dp[n - 1][m - 1] + + +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): + dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] + return dp[m - 1] + + +"""ドライバーコード""" +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}") + + # 記憶化探索 + mem = [[-1] * m for _ in range(n)] + res = min_path_sum_dfs_mem(grid, mem, n - 1, m - 1) + print(f"左上から右下角への最小パス和は {res}") + + # 動的プログラミング + res = min_path_sum_dp(grid) + print(f"左上から右下角への最小パス和は {res}") + + # 空間最適化動的プログラミング + res = min_path_sum_dp_comp(grid) + print(f"左上から右下角への最小パス和は {res}") \ No newline at end of file diff --git a/ja/codes/python/chapter_dynamic_programming/unbounded_knapsack.py b/ja/codes/python/chapter_dynamic_programming/unbounded_knapsack.py new file mode 100644 index 000000000..1751bd1c4 --- /dev/null +++ b/ja/codes/python/chapter_dynamic_programming/unbounded_knapsack.py @@ -0,0 +1,55 @@ +""" +File: unbounded_knapsack.py +Created Time: 2023-07-10 +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)] + # 状態遷移 + for i in range(1, n + 1): + for c in range(1, cap + 1): + 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] + + +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 を選択しない + dp[c] = dp[c] + else: + # アイテム i を選択しないのと選択するのとで大きい値 + dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) + return dp[cap] + + +"""ドライバーコード""" +if __name__ == "__main__": + wgt = [1, 2, 3] + val = [5, 11, 15] + cap = 4 + + # 動的プログラミング + res = unbounded_knapsack_dp(wgt, val, cap) + print(f"ナップサック容量を超えないアイテムの最大値は {res}") + + # 空間最適化動的プログラミング + res = unbounded_knapsack_dp_comp(wgt, val, cap) + print(f"ナップサック容量を超えないアイテムの最大値は {res}") \ No newline at end of file diff --git a/ja/codes/python/chapter_graph/graph_adjacency_list.py b/ja/codes/python/chapter_graph/graph_adjacency_list.py new file mode 100644 index 000000000..67ea4a03c --- /dev/null +++ b/ja/codes/python/chapter_graph/graph_adjacency_list.py @@ -0,0 +1,111 @@ +""" +File: graph_adjacency_list.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import Vertex, vals_to_vets + + +class GraphAdjList: + """隣接リストに基づく無向グラフクラス""" + + def __init__(self, edges: list[list[Vertex]]): + """コンストラクタ""" + # 隣接リスト、キー: 頂点、値: その頂点の隣接する全頂点 + self.adj_list = dict[Vertex, list[Vertex]]() + # すべての頂点と辺を追加 + for edge in edges: + self.add_vertex(edge[0]) + self.add_vertex(edge[1]) + self.add_edge(edge[0], edge[1]) + + def size(self) -> int: + """頂点数を取得""" + return len(self.adj_list) + + def add_edge(self, vet1: Vertex, vet2: Vertex): + """辺を追加""" + if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: + raise ValueError() + # 辺 vet1 - vet2 を追加 + self.adj_list[vet1].append(vet2) + self.adj_list[vet2].append(vet1) + + def remove_edge(self, vet1: Vertex, vet2: Vertex): + """辺を削除""" + if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: + raise ValueError() + # 辺 vet1 - vet2 を削除 + self.adj_list[vet1].remove(vet2) + self.adj_list[vet2].remove(vet1) + + def add_vertex(self, vet: Vertex): + """頂点を追加""" + 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に対応する連結リストを削除 + self.adj_list.pop(vet) + # 他の頂点の連結リストを走査し、vetを含むすべての辺を削除 + for vertex in self.adj_list: + if vet in self.adj_list[vertex]: + self.adj_list[vertex].remove(vet) + + def print(self): + """隣接リストを出力""" + print("隣接リスト =") + for vertex in self.adj_list: + tmp = [v.val for v in self.adj_list[vertex]] + print(f"{vertex.val}: {tmp},") + + +"""ドライバコード""" +if __name__ == "__main__": + # 無向グラフを初期化 + 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(edges) + print("\n初期化後、グラフは") + graph.print() + + # 辺を追加 + # 頂点1、2 つまり v[0], v[2] + graph.add_edge(v[0], v[2]) + print("\n辺1-2を追加後、グラフは") + graph.print() + + # 辺を削除 + # 頂点1、3 つまり v[0], v[1] + graph.remove_edge(v[0], v[1]) + print("\n辺1-3を削除後、グラフは") + graph.print() + + # 頂点を追加 + v5 = Vertex(6) + graph.add_vertex(v5) + print("\n頂点6を追加後、グラフは") + graph.print() + + # 頂点を削除 + # 頂点3 つまり v[1] + graph.remove_vertex(v[1]) + print("\n頂点3を削除後、グラフは") + graph.print() \ No newline at end of file diff --git a/ja/codes/python/chapter_graph/graph_adjacency_matrix.py b/ja/codes/python/chapter_graph/graph_adjacency_matrix.py new file mode 100644 index 000000000..2646320b7 --- /dev/null +++ b/ja/codes/python/chapter_graph/graph_adjacency_matrix.py @@ -0,0 +1,116 @@ +""" +File: graph_adjacency_matrix.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import Vertex, print_matrix + + +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要素は頂点インデックスを表す + for e in edges: + self.add_edge(e[0], e[1]) + + def size(self) -> int: + """頂点数を取得""" + return len(self.vertices) + + def add_vertex(self, val: int): + """頂点を追加""" + n = self.size() + # 頂点リストに新しい頂点値を追加 + self.vertices.append(val) + # 隣接行列に行を追加 + new_row = [0] * n + self.adj_mat.append(new_row) + # 隣接行列に列を追加 + for row in self.adj_mat: + row.append(0) + + def remove_vertex(self, index: int): + """頂点を削除""" + if index >= self.size(): + raise IndexError() + # 頂点リストから`index`の頂点を削除 + self.vertices.pop(index) + # 隣接行列から`index`の行を削除 + self.adj_mat.pop(index) + # 隣接行列から`index`の列を削除 + for row in self.adj_mat: + row.pop(index) + + def add_edge(self, i: int, j: int): + """辺を追加""" + # パラメータi、jは頂点要素のインデックスに対応 + # インデックスの範囲外と等価性を処理 + if i < 0 or j < 0 or i >= self.size() or j >= self.size() or i == j: + raise IndexError() + # 無向グラフでは、隣接行列は主対角線について対称、すなわち (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は頂点要素のインデックスに対応 + # インデックスの範囲外と等価性を処理 + 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 + self.adj_mat[j][i] = 0 + + def print(self): + """隣接行列を出力""" + print("頂点リスト =", self.vertices) + print("隣接行列 =") + print_matrix(self.adj_mat) + + +"""ドライバコード""" +if __name__ == "__main__": + # 無向グラフを初期化 + # edges要素は頂点インデックスを表す + 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 + graph.add_edge(0, 2) + print("\n辺1-2を追加後、グラフは") + graph.print() + + # 辺を削除 + # 頂点1、3のインデックスはそれぞれ0、1 + graph.remove_edge(0, 1) + print("\n辺1-3を削除後、グラフは") + graph.print() + + # 頂点を追加 + graph.add_vertex(6) + print("\n頂点6を追加後、グラフは") + graph.print() + + # 頂点を削除 + # 頂点3のインデックスは1 + graph.remove_vertex(1) + print("\n頂点3を削除後、グラフは") + graph.print() \ No newline at end of file diff --git a/ja/codes/python/chapter_graph/graph_bfs.py b/ja/codes/python/chapter_graph/graph_bfs.py new file mode 100644 index 000000000..4462c5290 --- /dev/null +++ b/ja/codes/python/chapter_graph/graph_bfs.py @@ -0,0 +1,64 @@ +""" +File: graph_bfs.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import Vertex, vals_to_vets, vets_to_vals +from collections import deque +from graph_adjacency_list import GraphAdjList + + +def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: + """幅優先走査""" + # 隣接リストを使用してグラフを表現し、指定された頂点のすべての隣接頂点を取得 + # 頂点走査シーケンス + res = [] + # ハッシュセット、訪問済み頂点を記録するために使用 + visited = set[Vertex]([start_vet]) + # BFSを実装するために使用されるキュー + que = deque[Vertex]([start_vet]) + # 頂点vetから開始し、すべての頂点が訪問されるまでループ + while len(que) > 0: + 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) # 頂点を訪問済みとしてマーク + # 頂点走査シーケンスを返す + return res + + +"""ドライバコード""" +if __name__ == "__main__": + # 無向グラフを初期化 + 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(edges) + print("\n初期化後、グラフは") + graph.print() + + # 幅優先走査 + res = graph_bfs(graph, v[0]) + print("\n幅優先走査(BFS)の頂点シーケンスは") + print(vets_to_vals(res)) \ No newline at end of file diff --git a/ja/codes/python/chapter_graph/graph_dfs.py b/ja/codes/python/chapter_graph/graph_dfs.py new file mode 100644 index 000000000..c6b150b7e --- /dev/null +++ b/ja/codes/python/chapter_graph/graph_dfs.py @@ -0,0 +1,57 @@ +""" +File: graph_dfs.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import Vertex, vets_to_vals, vals_to_vets +from graph_adjacency_list import GraphAdjList + + +def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex): + """深さ優先走査のヘルパー関数""" + res.append(vet) # 訪問済み頂点を記録 + visited.add(vet) # 頂点を訪問済みとしてマーク + # その頂点のすべての隣接頂点を走査 + for adjVet in graph.adj_list[vet]: + if adjVet in visited: + 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 + + +"""ドライバコード""" +if __name__ == "__main__": + # 無向グラフを初期化 + 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(edges) + print("\n初期化後、グラフは") + graph.print() + + # 深さ優先走査 + res = graph_dfs(graph, v[0]) + print("\n深さ優先走査(DFS)の頂点シーケンスは") + print(vets_to_vals(res)) \ No newline at end of file diff --git a/ja/codes/python/chapter_greedy/coin_change_greedy.py b/ja/codes/python/chapter_greedy/coin_change_greedy.py new file mode 100644 index 000000000..ca8592204 --- /dev/null +++ b/ja/codes/python/chapter_greedy/coin_change_greedy.py @@ -0,0 +1,48 @@ +""" +File: coin_change_greedy.py +Created Time: 2023-07-18 +Author: krahets (krahets@163.com) +""" + + +def coin_change_greedy(coins: list[int], amt: int) -> int: + """硬貨交換:貪欲法""" + # coins リストがソートされていると仮定 + i = len(coins) - 1 + count = 0 + # 残り金額がなくなるまで貪欲選択をループ + while amt > 0: + # 残り金額に最も近く、それより小さい硬貨を見つける + while i > 0 and coins[i] > amt: + i -= 1 + # coins[i] を選択 + amt -= coins[i] + count += 1 + # 実行可能な解が見つからない場合、-1 を返す + return count if amt == 0 else -1 + + +"""ドライバーコード""" +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}") + + # 貪欲法:大域最適解の発見を保証できない + 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") + + # 貪欲法:大域最適解の発見を保証できない + 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 diff --git a/ja/codes/python/chapter_greedy/fractional_knapsack.py b/ja/codes/python/chapter_greedy/fractional_knapsack.py new file mode 100644 index 000000000..950c68422 --- /dev/null +++ b/ja/codes/python/chapter_greedy/fractional_knapsack.py @@ -0,0 +1,46 @@ +""" +File: fractional_knapsack.py +Created Time: 2023-07-19 +Author: krahets (krahets@163.com) +""" + + +class Item: + """アイテム""" + + def __init__(self, w: int, v: int): + self.w = w # アイテムの重量 + self.v = v # アイテムの価値 + + +def fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int: + """分数ナップサック:貪欲法""" + # アイテムリストを作成、2 つの属性を含む:重量、価値 + items = [Item(w, v) for w, v in zip(wgt, val)] + # 単位価値 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 + + +"""ドライバーコード""" +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 diff --git a/ja/codes/python/chapter_greedy/max_capacity.py b/ja/codes/python/chapter_greedy/max_capacity.py new file mode 100644 index 000000000..f3b37e106 --- /dev/null +++ b/ja/codes/python/chapter_greedy/max_capacity.py @@ -0,0 +1,33 @@ +""" +File: max_capacity.py +Created Time: 2023-07-21 +Author: krahets (krahets@163.com) +""" + + +def max_capacity(ht: list[int]) -> int: + """最大容量:貪欲法""" + # i、j を初期化、配列の両端で分割させる + i, j = 0, len(ht) - 1 + # 初期最大容量は 0 + res = 0 + # 2 つの板が出会うまで貪欲選択をループ + while i < j: + # 最大容量を更新 + cap = min(ht[i], ht[j]) * (j - i) + res = max(res, cap) + # 短い板を内側に移動 + if ht[i] < ht[j]: + i += 1 + else: + j -= 1 + return res + + +"""ドライバーコード""" +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 diff --git a/ja/codes/python/chapter_greedy/max_product_cutting.py b/ja/codes/python/chapter_greedy/max_product_cutting.py new file mode 100644 index 000000000..192e53a5e --- /dev/null +++ b/ja/codes/python/chapter_greedy/max_product_cutting.py @@ -0,0 +1,33 @@ +""" +File: max_product_cutting.py +Created Time: 2023-07-21 +Author: krahets (krahets@163.com) +""" + +import math + + +def max_product_cutting(n: int) -> int: + """切断の最大積:貪欲法""" + # n <= 3 の場合、1 を切り出す必要がある + if n <= 3: + return 1 * (n - 1) + # 貪欲的に 3 を切り出す、a は 3 の個数、b は余り + a, b = n // 3, 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)) + + +"""ドライバーコード""" +if __name__ == "__main__": + n = 58 + + # 貪欲アルゴリズム + res = max_product_cutting(n) + print(f"切断の最大積は {res}") \ No newline at end of file diff --git a/ja/codes/python/chapter_hashing/array_hash_map.py b/ja/codes/python/chapter_hashing/array_hash_map.py new file mode 100644 index 000000000..f09456a62 --- /dev/null +++ b/ja/codes/python/chapter_hashing/array_hash_map.py @@ -0,0 +1,117 @@ +""" +File: array_hash_map.py +Created Time: 2022-12-14 +Author: msk397 (machangxinq@gmail.com) +""" + + +class Pair: + """キー値ペア""" + + def __init__(self, key: int, val: str): + self.key = key + self.val = val + + +class ArrayHashMap: + """配列実装に基づくハッシュテーブル""" + + def __init__(self): + """コンストラクタ""" + # 100個のバケットを含む配列を初期化 + self.buckets: list[Pair | None] = [None] * 100 + + def hash_func(self, key: int) -> int: + """ハッシュ関数""" + index = key % 100 + return index + + def get(self, key: int) -> str: + """照会操作""" + index: int = self.hash_func(key) + pair: Pair = self.buckets[index] + if pair is None: + return None + return pair.val + + def put(self, key: int, val: str): + """追加操作""" + pair = Pair(key, val) + index: int = self.hash_func(key) + self.buckets[index] = pair + + def remove(self, key: int): + """削除操作""" + index: int = self.hash_func(key) + # None に設定し、削除を表現 + self.buckets[index] = None + + def entry_set(self) -> list[Pair]: + """すべてのキー値ペアを取得""" + result: list[Pair] = [] + for pair in self.buckets: + if pair is not None: + result.append(pair) + return result + + def key_set(self) -> list[int]: + """すべてのキーを取得""" + result = [] + for pair in self.buckets: + if pair is not None: + result.append(pair.key) + return result + + def value_set(self) -> list[str]: + """すべての値を取得""" + result = [] + for pair in self.buckets: + if pair is not None: + result.append(pair.val) + return result + + def print(self): + """ハッシュテーブルを出力""" + for pair in self.buckets: + if pair is not None: + print(pair.key, "->", pair.val) + + +"""Driver Code""" +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") + hmap.print() + + # 照会操作 + # ハッシュテーブルにキーを入力し、値を取得 + name = hmap.get(15937) + print("\n学生ID 15937 を入力、名前 " + name + " が見つかりました") + + # 削除操作 + # ハッシュテーブルからキー値ペア (key, value) を削除 + hmap.remove(10583) + print("\n10583 を削除後、ハッシュテーブルは\nKey -> Value") + hmap.print() + + # ハッシュテーブルを走査 + print("\nキー値ペアを走査 Key->Value") + for pair in hmap.entry_set(): + print(pair.key, "->", pair.val) + + print("\nキーを個別に走査 Key") + for key in hmap.key_set(): + print(key) + + print("\n値を個別に走査 Value") + for val in hmap.value_set(): + print(val) \ No newline at end of file diff --git a/ja/codes/python/chapter_hashing/built_in_hash.py b/ja/codes/python/chapter_hashing/built_in_hash.py new file mode 100644 index 000000000..d8d624a70 --- /dev/null +++ b/ja/codes/python/chapter_hashing/built_in_hash.py @@ -0,0 +1,37 @@ +""" +File: built_in_hash.py +Created Time: 2023-06-15 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode + +"""Driver Code""" +if __name__ == "__main__": + num = 3 + hash_num = hash(num) + print(f"整数 {num} のハッシュ値は {hash_num}") + + bol = True + hash_bol = hash(bol) + print(f"ブール値 {bol} のハッシュ値は {hash_bol}") + + dec = 3.14159 + hash_dec = hash(dec) + print(f"小数 {dec} のハッシュ値は {hash_dec}") + + str = "Hello algorithm" + hash_str = hash(str) + print(f"文字列 {str} のハッシュ値は {hash_str}") + + tup = (12836, "Ha") + 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 diff --git a/ja/codes/python/chapter_hashing/hash_map.py b/ja/codes/python/chapter_hashing/hash_map.py new file mode 100644 index 000000000..4491b45c9 --- /dev/null +++ b/ja/codes/python/chapter_hashing/hash_map.py @@ -0,0 +1,50 @@ +""" +File: hash_map.py +Created Time: 2022-12-14 +Author: msk397 (machangxinq@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import print_dict + +"""Driver Code""" +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") + print_dict(hmap) + + # 照会操作 + # ハッシュテーブルにキーを入力し、値を取得 + name: str = hmap[15937] + print("\n学生ID 15937 を入力、名前 " + name + " が見つかりました") + + # 削除操作 + # ハッシュテーブルからキー値ペア (key, value) を削除 + hmap.pop(10583) + print("\n10583 を削除後、ハッシュテーブルは\nKey -> Value") + print_dict(hmap) + + # ハッシュテーブルを走査 + print("\nキー値ペアを走査 Key->Value") + for key, value in hmap.items(): + print(key, "->", value) + + print("\nキーを個別に走査 Key") + for key in hmap.keys(): + print(key) + + print("\n値を個別に走査 Value") + for val in hmap.values(): + print(val) \ No newline at end of file diff --git a/ja/codes/python/chapter_hashing/hash_map_chaining.py b/ja/codes/python/chapter_hashing/hash_map_chaining.py new file mode 100644 index 000000000..803fac5f2 --- /dev/null +++ b/ja/codes/python/chapter_hashing/hash_map_chaining.py @@ -0,0 +1,118 @@ +""" +File: hash_map_chaining.py +Created Time: 2023-06-13 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +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.buckets = [[] for _ in range(self.capacity)] # バケット配列 + + def hash_func(self, key: int) -> int: + """ハッシュ関数""" + return key % self.capacity + + def load_factor(self) -> float: + """負荷率""" + return self.size / self.capacity + + def get(self, key: int) -> str | None: + """照会操作""" + index = self.hash_func(key) + bucket = self.buckets[index] + # バケットを走査し、キーが見つかれば対応する val を返す + for pair in bucket: + if pair.key == key: + return pair.val + # キーが見つからない場合、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 を更新して返す + for pair in bucket: + if pair.key == key: + pair.val = val + return + # キーが見つからない場合、キー値ペアを末尾に追加 + pair = Pair(key, val) + bucket.append(pair) + self.size += 1 + + def remove(self, key: int): + """削除操作""" + index = self.hash_func(key) + bucket = self.buckets[index] + # バケットを走査し、その中からキー値ペアを削除 + for pair in bucket: + if pair.key == key: + bucket.remove(pair) + self.size -= 1 + break + + 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) + + def print(self): + """ハッシュテーブルを出力""" + for bucket in self.buckets: + res = [] + for pair in bucket: + res.append(str(pair.key) + " -> " + pair.val) + print(res) + + +"""Driver Code""" +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, ...]") + hashmap.print() + + # 照会操作 + # ハッシュテーブルにキーを入力し、値を取得 + name = hashmap.get(13276) + print("\n学生ID 13276 を入力、名前 " + name + " が見つかりました") + + # 削除操作 + # ハッシュテーブルからキー値ペア (key, value) を削除 + hashmap.remove(12836) + print("\n12836 を削除後、ハッシュテーブルは\n[Key1 -> Value1, Key2 -> Value2, ...]") + hashmap.print() \ No newline at end of file diff --git a/ja/codes/python/chapter_hashing/hash_map_open_addressing.py b/ja/codes/python/chapter_hashing/hash_map_open_addressing.py new file mode 100644 index 000000000..43347084a --- /dev/null +++ b/ja/codes/python/chapter_hashing/hash_map_open_addressing.py @@ -0,0 +1,138 @@ +""" +File: hash_map_open_addressing.py +Created Time: 2023-06-13 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from chapter_hashing.array_hash_map import Pair + + +class HashMapOpenAddressing: + """オープンアドレス法ハッシュテーブル""" + + def __init__(self): + """コンストラクタ""" + 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") # 削除マーク + + def hash_func(self, key: int) -> int: + """ハッシュ関数""" + return key % self.capacity + + def load_factor(self) -> float: + """負荷率""" + return self.size / self.capacity + + def find_bucket(self, key: int) -> int: + """key に対応するバケットインデックスを検索""" + index = self.hash_func(key) + first_tombstone = -1 + # 線形探査、空のバケットに遭遇したらブレーク + while self.buckets[index] is not None: + # キーに遭遇した場合、対応するバケットインデックスを返す + 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 # バケットインデックスを返す + # 最初に遭遇した削除マークを記録 + if first_tombstone == -1 and self.buckets[index] is self.TOMBSTONE: + first_tombstone = index + # バケットインデックスを計算、末尾を超えた場合は先頭に戻る + index = (index + 1) % self.capacity + # キーが存在しない場合、挿入ポイントのインデックスを返す + return index if first_tombstone == -1 else first_tombstone + + def get(self, key: int) -> str: + """照会操作""" + # key に対応するバケットインデックスを検索 + index = self.find_bucket(key) + # キー値ペアが見つかれば、対応する val を返す + if self.buckets[index] not in [None, self.TOMBSTONE]: + return self.buckets[index].val + # キー値ペアが存在しない場合、None を返す + return None + + def put(self, key: int, val: str): + """追加操作""" + # 負荷率が閾値を超えた場合、拡張を実行 + if self.load_factor() > self.load_thres: + self.extend() + # key に対応するバケットインデックスを検索 + index = self.find_bucket(key) + # キー値ペアが見つかれば、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 に対応するバケットインデックスを検索 + 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) + + def print(self): + """ハッシュテーブルを出力""" + for pair in self.buckets: + if pair is None: + print("None") + elif pair is self.TOMBSTONE: + print("TOMBSTONE") + else: + print(pair.key, "->", pair.val) + + +"""Driver Code""" +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") + hashmap.print() + + # 照会操作 + # ハッシュテーブルにキーを入力し、値 val を取得 + name = hashmap.get(13276) + print("\n学生ID 13276 を入力、名前 " + name + " が見つかりました") + + # 削除操作 + # ハッシュテーブルからキー値ペア (key, val) を削除 + hashmap.remove(16750) + print("\n16750 を削除後、ハッシュテーブルは\nKey -> Value") + hashmap.print() \ No newline at end of file diff --git a/ja/codes/python/chapter_hashing/simple_hash.py b/ja/codes/python/chapter_hashing/simple_hash.py new file mode 100644 index 000000000..9ccbc46c4 --- /dev/null +++ b/ja/codes/python/chapter_hashing/simple_hash.py @@ -0,0 +1,58 @@ +""" +File: simple_hash.py +Created Time: 2023-06-15 +Author: krahets (krahets@163.com) +""" + + +def add_hash(key: str) -> int: + """加法ハッシュ""" + hash = 0 + modulus = 1000000007 + for c in key: + hash += ord(c) + return hash % modulus + + +def mul_hash(key: str) -> int: + """乗法ハッシュ""" + hash = 0 + modulus = 1000000007 + for c in key: + hash = 31 * hash + ord(c) + return hash % modulus + + +def xor_hash(key: str) -> int: + """XORハッシュ""" + hash = 0 + modulus = 1000000007 + for c in key: + hash ^= ord(c) + return hash % modulus + + +def rot_hash(key: str) -> int: + """回転ハッシュ""" + hash = 0 + modulus = 1000000007 + for c in key: + hash = (hash << 4) ^ (hash >> 28) ^ ord(c) + return hash % modulus + + +"""Driver Code""" +if __name__ == "__main__": + key = "Hello algorithm" + + hash = add_hash(key) + print(f"加法ハッシュ値は {hash}") + + hash = mul_hash(key) + print(f"乗法ハッシュ値は {hash}") + + hash = xor_hash(key) + print(f"XORハッシュ値は {hash}") + + hash = rot_hash(key) + print(f"回転ハッシュ値は {hash}") \ No newline at end of file diff --git a/ja/codes/python/chapter_heap/heap.py b/ja/codes/python/chapter_heap/heap.py new file mode 100644 index 000000000..c7ecba5c0 --- /dev/null +++ b/ja/codes/python/chapter_heap/heap.py @@ -0,0 +1,71 @@ +""" +File: heap.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import print_heap + +import heapq + + +def test_push(heap: list, val: int, flag: int = 1): + 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} がヒープから出た後") + print_heap([flag * val for val in heap]) + + +"""ドライバコード""" +if __name__ == "__main__": + # 最小ヒープを初期化 + min_heap, flag = [], 1 + # 最大ヒープを初期化 + max_heap, flag = [], -1 + + print("\n以下のテストケースは最大ヒープ用です") + # PythonのheapqモジュールはデフォルトでMinHeapを実装 + # ヒープに入れる前に「要素を反転」することを考慮し、比較演算子を逆転させて最大ヒープを実装 + # この例では、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}") + + # ヒープの先頭要素をポップ + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + test_pop(max_heap, flag) + + # ヒープのサイズを取得 + size: int = len(max_heap) + print(f"\nヒープの要素数は {size}") + + # ヒープが空かどうかを判定 + is_empty: bool = not max_heap + print(f"\nヒープは空ですか {is_empty}") + + # リストを入力してヒープを構築 + # 時間複雑度は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 diff --git a/ja/codes/python/chapter_heap/my_heap.py b/ja/codes/python/chapter_heap/my_heap.py new file mode 100644 index 000000000..e0a9c71d1 --- /dev/null +++ b/ja/codes/python/chapter_heap/my_heap.py @@ -0,0 +1,137 @@ +""" +File: my_heap.py +Created Time: 2023-02-23 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import print_heap + + +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 # 整数除算で切り下げ + + def swap(self, i: int, j: int): + """要素を交換""" + self.max_heap[i], self.max_heap[j] = self.max_heap[j], self.max_heap[i] + + def size(self) -> int: + """ヒープのサイズを取得""" + return len(self.max_heap) + + def is_empty(self) -> bool: + """ヒープが空かどうかを判定""" + 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から開始して、下から上へヒープ化""" + while True: + # ノードiの親ノードを取得 + p = self.parent(i) + # 「ルートノードを越える」または「ノードが修復不要」の場合、ヒープ化を終了 + if p < 0 or self.max_heap[i] <= self.max_heap[p]: + break + # 2つのノードを交換 + self.swap(i, p) + # 上向きのループヒープ化 + i = p + + def pop(self) -> int: + """要素をヒープから出す""" + # 空の処理 + if self.is_empty(): + raise IndexError("Heap is empty") + # ルートノードと最右端の葉ノードを交換(最初の要素と最後の要素を交換) + self.swap(0, self.size() - 1) + # ノードを削除 + val = self.max_heap.pop() + # 上から下へヒープ化 + self.sift_down(0) + # ヒープの先頭要素を返す + return val + + def sift_down(self, i: int): + """ノードiから開始して、上から下へヒープ化""" + while True: + # 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が範囲外の場合、さらなるヒープ化は不要、ブレーク + if ma == i: + break + # 2つのノードを交換 + self.swap(i, ma) + # 下向きのループヒープ化 + i = ma + + def print(self): + """ヒープを出力(二分木)""" + print_heap(self.max_heap) + + +"""ドライバコード""" +if __name__ == "__main__": + # 最大ヒープを初期化 + max_heap = MaxHeap([9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2]) + print("\nリストを入力してヒープを構築") + max_heap.print() + + # ヒープの先頭要素にアクセス + peek = max_heap.peek() + print(f"\nヒープの先頭要素は {peek}") + + # ヒープに要素をプッシュ + val = 7 + max_heap.push(val) + print(f"\n要素 {val} をヒープにプッシュ後") + max_heap.print() + + # ヒープの先頭要素をポップ + peek = max_heap.pop() + print(f"\nヒープの先頭要素 {peek} がヒープから出た後") + max_heap.print() + + # ヒープのサイズを取得 + size = max_heap.size() + print(f"\nヒープの要素数は {size}") + + # ヒープが空かどうかを判定 + is_empty = max_heap.is_empty() + print(f"\nヒープは空ですか {is_empty}") \ No newline at end of file diff --git a/ja/codes/python/chapter_heap/top_k.py b/ja/codes/python/chapter_heap/top_k.py new file mode 100644 index 000000000..bdc386629 --- /dev/null +++ b/ja/codes/python/chapter_heap/top_k.py @@ -0,0 +1,39 @@ +""" +File: top_k.py +Created Time: 2023-06-10 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import print_heap + +import heapq + + +def top_k_heap(nums: list[int], k: int) -> list[int]: + """ヒープを使用して配列内の最大k個の要素を見つける""" + # 最小ヒープを初期化 + heap = [] + # 配列の最初のk個の要素をヒープに入力 + for i in range(k): + heapq.heappush(heap, nums[i]) + # 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 + + +"""ドライバコード""" +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 diff --git a/ja/codes/python/chapter_searching/binary_search.py b/ja/codes/python/chapter_searching/binary_search.py new file mode 100644 index 000000000..0636496cc --- /dev/null +++ b/ja/codes/python/chapter_searching/binary_search.py @@ -0,0 +1,52 @@ +""" +File: binary_search.py +Created Time: 2022-11-26 +Author: timi (xisunyy@163.com) +""" + + +def binary_search(nums: list[int], target: int) -> int: + """二分探索(両端閉区間)""" + # 両端閉区間 [0, n-1] を初期化、すなわち i, j はそれぞれ配列の最初の要素と最後の要素を指す + i, j = 0, len(nums) - 1 + # 検索区間が空になるまでループ(i > j のとき空) + while i <= j: + # 理論的には、Pythonの数値は無限に大きくなることができる(メモリサイズに依存)ため、大きな数のオーバーフローを考慮する必要はない + m = i + (j - i) // 2 # 中点インデックス m を計算 + if nums[m] < target: + i = m + 1 # この場合、target は区間 [m+1, j] にあることを示す + elif nums[m] > target: + j = m - 1 # この場合、target は区間 [i, m-1] にあることを示す + else: + return m # ターゲット要素が見つかったため、そのインデックスを返す + return -1 # ターゲット要素が見つからなかったため、-1 を返す + + +def binary_search_lcro(nums: list[int], target: int) -> int: + """二分探索(左閉右開区間)""" + # 左閉右開区間 [0, n) を初期化、すなわち i, j はそれぞれ配列の最初の要素と最後の要素+1を指す + i, j = 0, len(nums) + # 検索区間が空になるまでループ(i = j のとき空) + while i < j: + m = i + (j - i) // 2 # 中点インデックス m を計算 + if nums[m] < target: + i = m + 1 # この場合、target は区間 [m+1, j) にあることを示す + elif nums[m] > target: + j = m # この場合、target は区間 [i, m) にあることを示す + else: + return m # ターゲット要素が見つかったため、そのインデックスを返す + return -1 # ターゲット要素が見つからなかったため、-1 を返す + + +"""ドライバーコード""" +if __name__ == "__main__": + target = 6 + nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] + + # 二分探索(両端閉区間) + index = binary_search(nums, target) + print("ターゲット要素 6 のインデックス =", index) + + # 二分探索(左閉右開区間) + index = binary_search_lcro(nums, target) + print("ターゲット要素 6 のインデックス =", index) \ No newline at end of file diff --git a/ja/codes/python/chapter_searching/binary_search_edge.py b/ja/codes/python/chapter_searching/binary_search_edge.py new file mode 100644 index 000000000..703ddb563 --- /dev/null +++ b/ja/codes/python/chapter_searching/binary_search_edge.py @@ -0,0 +1,49 @@ +""" +File: binary_search_edge.py +Created Time: 2023-08-04 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from binary_search_insertion import binary_search_insertion + + +def binary_search_left_edge(nums: list[int], target: int) -> int: + """最左端のターゲットの二分探索""" + # ターゲットの挿入位置を見つけることと同等 + i = binary_search_insertion(nums, target) + # ターゲットが見つからなかった場合、-1 を返す + if i == len(nums) or nums[i] != target: + return -1 + # ターゲットが見つかった場合、インデックス i を返す + return i + + +def binary_search_right_edge(nums: list[int], target: int) -> int: + """最右端のターゲットの二分探索""" + # 最左端のターゲット + 1 を見つけることに変換 + i = binary_search_insertion(nums, target + 1) + # j は最右端のターゲットを指し、i はターゲットより大きい最初の要素を指す + j = i - 1 + # ターゲットが見つからなかった場合、-1 を返す + if j == -1 or nums[j] != target: + return -1 + # ターゲットが見つかった場合、インデックス j を返す + return j + + +"""ドライバーコード""" +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}") + index = binary_search_right_edge(nums, target) + print(f"最右端の要素 {target} のインデックスは {index}") \ No newline at end of file diff --git a/ja/codes/python/chapter_searching/binary_search_insertion.py b/ja/codes/python/chapter_searching/binary_search_insertion.py new file mode 100644 index 000000000..0f48d6541 --- /dev/null +++ b/ja/codes/python/chapter_searching/binary_search_insertion.py @@ -0,0 +1,54 @@ +""" +File: binary_search_insertion.py +Created Time: 2023-08-04 +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] を初期化 + while i <= j: + m = i + (j - i) // 2 # 中点インデックス m を計算 + if nums[m] < target: + i = m + 1 # ターゲットは区間 [m+1, j] にある + elif nums[m] > target: + j = m - 1 # ターゲットは区間 [i, m-1] にある + else: + return m # ターゲットが見つかった場合、挿入位置 m を返す + # ターゲットが見つからなかった場合、挿入位置 i を返す + return i + + +def binary_search_insertion(nums: list[int], target: int) -> int: + """挿入位置の二分探索(重複要素あり)""" + i, j = 0, len(nums) - 1 # 両端閉区間 [0, n-1] を初期化 + while i <= j: + m = i + (j - i) // 2 # 中点インデックス m を計算 + if nums[m] < target: + i = m + 1 # ターゲットは区間 [m+1, j] にある + elif nums[m] > target: + j = m - 1 # ターゲットは区間 [i, m-1] にある + else: + j = m - 1 # ターゲット未満の最初の要素は区間 [i, m-1] にある + # 挿入位置 i を返す + return i + + +"""ドライバーコード""" +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}") + + # 重複要素のある配列 + 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 diff --git a/ja/codes/python/chapter_searching/hashing_search.py b/ja/codes/python/chapter_searching/hashing_search.py new file mode 100644 index 000000000..ff6fffbb2 --- /dev/null +++ b/ja/codes/python/chapter_searching/hashing_search.py @@ -0,0 +1,51 @@ +""" +File: hashing_search.py +Created Time: 2022-11-26 +Author: timi (xisunyy@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode, list_to_linked_list + + +def hashing_search_array(hmap: dict[int, int], target: int) -> int: + """ハッシュ探索(配列)""" + # ハッシュテーブルのキー:ターゲット要素、値:インデックス + # ハッシュテーブルがこのキーを含まない場合、-1 を返す + return hmap.get(target, -1) + + +def hashing_search_linkedlist( + hmap: dict[int, ListNode], target: int +) -> ListNode | None: + """ハッシュ探索(連結リスト)""" + # ハッシュテーブルのキー:ターゲット要素、値:ノードオブジェクト + # ハッシュテーブルがこのキーを含まない場合、None を返す + return hmap.get(target, None) + + +"""ドライバーコード""" +if __name__ == "__main__": + target = 3 + + # ハッシュ探索(配列) + nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] + # ハッシュテーブルを初期化 + map0 = dict[int, int]() + for i in range(len(nums)): + map0[nums[i]] = i # キー:要素、値:インデックス + index: int = hashing_search_array(map0, target) + print("ターゲット要素 3 のインデックス =", index) + + # ハッシュ探索(連結リスト) + head: ListNode = list_to_linked_list(nums) + # ハッシュテーブルを初期化 + map1 = dict[int, ListNode]() + while head: + map1[head.val] = head # キー:ノード値、値:ノード + head = head.next + node: ListNode = hashing_search_linkedlist(map1, target) + print("ターゲットノード値 3 に対応するノードオブジェクトは", node) \ No newline at end of file diff --git a/ja/codes/python/chapter_searching/linear_search.py b/ja/codes/python/chapter_searching/linear_search.py new file mode 100644 index 000000000..1fa74017f --- /dev/null +++ b/ja/codes/python/chapter_searching/linear_search.py @@ -0,0 +1,45 @@ +""" +File: linear_search.py +Created Time: 2022-11-26 +Author: timi (xisunyy@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode, list_to_linked_list + + +def linear_search_array(nums: list[int], target: int) -> int: + """線形探索(配列)""" + # 配列を走査 + for i in range(len(nums)): + if nums[i] == target: # ターゲット要素が見つかったため、そのインデックスを返す + return i + return -1 # ターゲット要素が見つからなかったため、-1 を返す + + +def linear_search_linkedlist(head: ListNode, target: int) -> ListNode | None: + """線形探索(連結リスト)""" + # リストを走査 + while head: + if head.val == target: # ターゲットノードが見つかったため、それを返す + return head + head = head.next + return None # ターゲットノードが見つからなかったため、None を返す + + +"""ドライバーコード""" +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) + + # 連結リストでの線形探索を実行 + head: ListNode = list_to_linked_list(nums) + node: ListNode | None = linear_search_linkedlist(head, target) + print("ターゲットノード値 3 に対応するノードオブジェクトは", node) \ No newline at end of file diff --git a/ja/codes/python/chapter_searching/two_sum.py b/ja/codes/python/chapter_searching/two_sum.py new file mode 100644 index 000000000..3e6a44f70 --- /dev/null +++ b/ja/codes/python/chapter_searching/two_sum.py @@ -0,0 +1,42 @@ +""" +File: two_sum.py +Created Time: 2022-11-25 +Author: krahets (krahets@163.com) +""" + + +def two_sum_brute_force(nums: list[int], target: int) -> list[int]: + """方法一:ブルートフォース列挙""" + # 二重ループ、時間計算量は O(n^2) + for i in range(len(nums) - 1): + for j in range(i + 1, len(nums)): + if nums[i] + nums[j] == target: + return [i, j] + return [] + + +def two_sum_hash_table(nums: list[int], target: int) -> list[int]: + """方法二:補助ハッシュテーブル""" + # 補助ハッシュテーブル、空間計算量は O(n) + dic = {} + # 単一ループ、時間計算量は O(n) + for i in range(len(nums)): + if target - nums[i] in dic: + return [dic[target - nums[i]], i] + dic[nums[i]] = i + return [] + + +"""ドライバーコード""" +if __name__ == "__main__": + # ======= テストケース ======= + nums = [2, 7, 11, 15] + target = 13 + + # ====== ドライバーコード ====== + # 方法一 + res: list[int] = two_sum_brute_force(nums, target) + print("方法一の結果 =", res) + # 方法二 + res: list[int] = two_sum_hash_table(nums, target) + print("方法二の結果 =", res) \ No newline at end of file diff --git a/ja/codes/python/chapter_sorting/bubble_sort.py b/ja/codes/python/chapter_sorting/bubble_sort.py new file mode 100644 index 000000000..903604fff --- /dev/null +++ b/ja/codes/python/chapter_sorting/bubble_sort.py @@ -0,0 +1,44 @@ +""" +File: bubble_sort.py +Created Time: 2022-11-25 +Author: timi (xisunyy@163.com) +""" + + +def bubble_sort(nums: list[int]): + """バブルソート""" + n = len(nums) + # 外側のループ:未ソート範囲は [0, i] + for i in range(n - 1, 0, -1): + # 内側のループ:未ソート範囲 [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] + + +def bubble_sort_with_flag(nums: list[int]): + """バブルソート(フラグによる最適化)""" + n = len(nums) + # 外側のループ:未ソート範囲は [0, i] + for i in range(n - 1, 0, -1): + 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 # 要素を交換したことを記録 + if not flag: + break # この回の「バブリング」で要素が交換されなかった場合、終了 + + +"""ドライバーコード""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + bubble_sort(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 diff --git a/ja/codes/python/chapter_sorting/bucket_sort.py b/ja/codes/python/chapter_sorting/bucket_sort.py new file mode 100644 index 000000000..8a482daa7 --- /dev/null +++ b/ja/codes/python/chapter_sorting/bucket_sort.py @@ -0,0 +1,35 @@ +""" +File: bucket_sort.py +Created Time: 2023-03-30 +Author: krahets (krahets@163.com) +""" + + +def bucket_sort(nums: list[float]): + """バケットソート""" + # k = n/2 個のバケットを初期化、各バケットに平均2個の要素を配置することを期待 + k = len(nums) // 2 + buckets = [[] for _ in range(k)] + # 1. 配列要素を各バケットに分散 + for num in nums: + # 入力データ範囲は [0, 1)、num * k を使用してインデックス範囲 [0, k-1] にマッピング + i = int(num * k) + # num をバケット i に追加 + buckets[i].append(num) + # 2. 各バケットをソート + for bucket in buckets: + # 組み込みソート関数を使用、他のソートアルゴリズムに置き換えることも可能 + bucket.sort() + # 3. バケットを走査して結果をマージ + i = 0 + for bucket in buckets: + for num in bucket: + nums[i] = num + i += 1 + + +if __name__ == "__main__": + # 入力データが浮動小数点数、範囲 [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 diff --git a/ja/codes/python/chapter_sorting/counting_sort.py b/ja/codes/python/chapter_sorting/counting_sort.py new file mode 100644 index 000000000..2c0bda919 --- /dev/null +++ b/ja/codes/python/chapter_sorting/counting_sort.py @@ -0,0 +1,64 @@ +""" +File: counting_sort.py +Created Time: 2023-03-21 +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. 各数字の出現回数を統計 + # counter[num] は num の出現回数を表す + counter = [0] * (m + 1) + for num in nums: + counter[num] += 1 + # 3. counter を走査し、各要素を元の配列 nums に埋め戻し + i = 0 + for num in range(m + 1): + for _ in range(counter[num]): + nums[i] = num + i += 1 + + +def counting_sort(nums: list[int]): + """計数ソート""" + # 完全な実装、オブジェクトのソートが可能で、安定ソート + # 1. 配列内の最大要素 m を統計 + m = max(nums) + # 2. 各数字の出現回数を統計 + # counter[num] は num の出現回数を表す + counter = [0] * (m + 1) + for num in nums: + counter[num] += 1 + # 3. counter の前置和を計算し、「出現回数」を「末尾インデックス」に変換 + # counter[num]-1 は res において num が最後に出現するインデックス + for i in range(m): + counter[i + 1] += counter[i] + # 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 を上書き + for i in range(n): + nums[i] = res[i] + + +"""ドライバーコード""" +if __name__ == "__main__": + nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] + + counting_sort_naive(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 diff --git a/ja/codes/python/chapter_sorting/heap_sort.py b/ja/codes/python/chapter_sorting/heap_sort.py new file mode 100644 index 000000000..28127c2eb --- /dev/null +++ b/ja/codes/python/chapter_sorting/heap_sort.py @@ -0,0 +1,45 @@ +""" +File: heap_sort.py +Created Time: 2023-05-24 +Author: krahets (krahets@163.com) +""" + + +def sift_down(nums: list[int], n: int, i: int): + """ヒープの長さが n、ノード i から上から下へヒープ化を開始""" + while True: + # i、l、r の中で最大のノードを判定し、ma とする + l = 2 * i + 1 + r = 2 * i + 2 + ma = i + if l < n and nums[l] > nums[ma]: + ma = l + if r < n and nums[r] > nums[ma]: + ma = r + # ノード i が最大または l、r のインデックスが範囲外の場合、さらなるヒープ化は不要、ループを抜ける + if ma == i: + break + # 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 回繰り返す + for i in range(len(nums) - 1, 0, -1): + # ルートノードと最も右の葉ノードを交換(最初の要素と最後の要素を交換) + nums[0], nums[i] = nums[i], nums[0] + # ルートノードから上から下へヒープ化を開始 + sift_down(nums, i, 0) + + +"""ドライバーコード""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + heap_sort(nums) + print("ヒープソート完了 nums =", nums) \ No newline at end of file diff --git a/ja/codes/python/chapter_sorting/insertion_sort.py b/ja/codes/python/chapter_sorting/insertion_sort.py new file mode 100644 index 000000000..0f11349d4 --- /dev/null +++ b/ja/codes/python/chapter_sorting/insertion_sort.py @@ -0,0 +1,25 @@ +""" +File: insertion_sort.py +Created Time: 2022-11-25 +Author: timi (xisunyy@163.com) +""" + + +def insertion_sort(nums: list[int]): + """挿入ソート""" + # 外側のループ:ソート済み範囲は [0, i-1] + for i in range(1, len(nums)): + base = nums[i] + j = i - 1 + # 内側のループ:base をソート済み範囲 [0, i-1] の正しい位置に挿入 + while j >= 0 and nums[j] > base: + nums[j + 1] = nums[j] # nums[j] を右に1つ移動 + j -= 1 + nums[j + 1] = base # base を正しい位置に代入 + + +"""ドライバーコード""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + insertion_sort(nums) + print("挿入ソート完了 nums =", nums) \ No newline at end of file diff --git a/ja/codes/python/chapter_sorting/merge_sort.py b/ja/codes/python/chapter_sorting/merge_sort.py new file mode 100644 index 000000000..0d6d3b663 --- /dev/null +++ b/ja/codes/python/chapter_sorting/merge_sort.py @@ -0,0 +1,55 @@ +""" +File: merge_sort.py +Created Time: 2022-11-25 +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 を作成してマージ結果を格納 + 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] + 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 range(0, len(tmp)): + nums[left + k] = tmp[k] + + +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) # 右サブ配列を再帰的に処理 + # マージ段階 + merge(nums, left, mid, right) + + +"""ドライバーコード""" +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 diff --git a/ja/codes/python/chapter_sorting/quick_sort.py b/ja/codes/python/chapter_sorting/quick_sort.py new file mode 100644 index 000000000..2582790f7 --- /dev/null +++ b/ja/codes/python/chapter_sorting/quick_sort.py @@ -0,0 +1,129 @@ +""" +File: quick_sort.py +Created Time: 2022-11-25 +Author: timi (xisunyy@163.com) +""" + + +class QuickSort: + """クイックソートクラス""" + + def partition(self, nums: list[int], left: int, right: int) -> int: + """分割""" + # nums[left] をピボットとして使用 + i, j = left, right + while i < j: + while i < j and nums[j] >= nums[left]: + j -= 1 # 右から左へピボットより小さい最初の要素を探す + while i < j and nums[i] <= nums[left]: + i += 1 # 左から右へピボットより大きい最初の要素を探す + # 要素を交換 + nums[i], nums[j] = nums[j], nums[i] + # ピボットを2つのサブ配列の境界に交換 + nums[i], nums[left] = nums[left], nums[i] + return i # ピボットのインデックスを返す + + def quick_sort(self, nums: list[int], left: int, right: int): + """クイックソート""" + # サブ配列の長さが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 QuickSortMedian: + """クイックソートクラス(中央値ピボット最適化)""" + + def median_three(self, nums: list[int], left: int, mid: int, right: int) -> int: + """3つの候補要素の中央値を選択""" + l, m, r = nums[left], nums[mid], nums[right] + if (l <= m <= r) or (r <= m <= l): + return mid # m は l と r の間 + if (m <= l <= r) or (r <= l <= m): + return left # l は m と r の間 + return right + + def partition(self, nums: list[int], left: int, right: int) -> int: + """分割(三点中央値)""" + # nums[left] をピボットとして使用 + med = self.median_three(nums, left, (left + right) // 2, right) + # 中央値を配列の最左端に交換 + nums[left], nums[med] = nums[med], nums[left] + # nums[left] をピボットとして使用 + i, j = left, right + while i < j: + while i < j and nums[j] >= nums[left]: + j -= 1 # 右から左へピボットより小さい最初の要素を探す + while i < j and nums[i] <= nums[left]: + i += 1 # 左から右へピボットより大きい最初の要素を探す + # 要素を交換 + nums[i], nums[j] = nums[j], nums[i] + # ピボットを2つのサブ配列の境界に交換 + nums[i], nums[left] = nums[left], nums[i] + return i # ピボットのインデックスを返す + + def quick_sort(self, nums: list[int], left: int, right: int): + """クイックソート""" + # サブ配列の長さが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] をピボットとして使用 + i, j = left, right + while i < j: + while i < j and nums[j] >= nums[left]: + j -= 1 # 右から左へピボットより小さい最初の要素を探す + while i < j and nums[i] <= nums[left]: + i += 1 # 左から右へピボットより大きい最初の要素を探す + # 要素を交換 + nums[i], nums[j] = nums[j], nums[i] + # ピボットを2つのサブ配列の境界に交換 + nums[i], nums[left] = nums[left], nums[i] + return i # ピボットのインデックスを返す + + def quick_sort(self, nums: list[int], left: int, right: int): + """クイックソート(末尾再帰最適化)""" + # サブ配列の長さが1のときに終了 + while left < right: + # 分割操作 + pivot = self.partition(nums, left, right) + # 2つのサブ配列のうち短い方に対してクイックソートを実行 + if pivot - left < right - pivot: + 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] + + +"""ドライバーコード""" +if __name__ == "__main__": + # クイックソート + nums = [2, 4, 1, 0, 3, 5] + QuickSort().quick_sort(nums, 0, len(nums) - 1) + print("クイックソート完了 nums =", nums) + + # クイックソート(中央値ピボット最適化) + nums1 = [2, 4, 1, 0, 3, 5] + QuickSortMedian().quick_sort(nums1, 0, len(nums1) - 1) + 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 diff --git a/ja/codes/python/chapter_sorting/radix_sort.py b/ja/codes/python/chapter_sorting/radix_sort.py new file mode 100644 index 000000000..ca8982da0 --- /dev/null +++ b/ja/codes/python/chapter_sorting/radix_sort.py @@ -0,0 +1,69 @@ +""" +File: radix_sort.py +Created Time: 2023-03-26 +Author: krahets (krahets@163.com) +""" + + +def digit(num: int, exp: int) -> int: + """要素 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のバケット配列が必要 + counter = [0] * 10 + n = len(nums) + # 数字 0~9 の出現回数を統計 + for i in range(n): + 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 = [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 を上書き + 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 = 1 -> exp = 1 + # k = 2 -> exp = 10 + # つまり、exp = 10^(k-1) + counting_sort_digit(nums, exp) + exp *= 10 + + +"""ドライバーコード""" +if __name__ == "__main__": + # 基数ソート + nums = [ + 10546151, + 35663510, + 42865989, + 34862445, + 81883077, + 88906420, + 72429244, + 30524779, + 82060337, + 63832996, + ] + radix_sort(nums) + print("基数ソート完了 nums =", nums) \ No newline at end of file diff --git a/ja/codes/python/chapter_sorting/selection_sort.py b/ja/codes/python/chapter_sorting/selection_sort.py new file mode 100644 index 000000000..fff78ce71 --- /dev/null +++ b/ja/codes/python/chapter_sorting/selection_sort.py @@ -0,0 +1,26 @@ +""" +File: selection_sort.py +Created Time: 2023-05-22 +Author: krahets (krahets@163.com) +""" + + +def selection_sort(nums: list[int]): + """選択ソート""" + n = len(nums) + # 外側のループ:未ソート範囲は [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] + + +"""ドライバーコード""" +if __name__ == "__main__": + nums = [4, 1, 3, 1, 5, 2] + selection_sort(nums) + print("選択ソート完了 nums =", nums) \ No newline at end of file diff --git a/ja/codes/python/chapter_stack_and_queue/array_deque.py b/ja/codes/python/chapter_stack_and_queue/array_deque.py new file mode 100644 index 000000000..a43f42f71 --- /dev/null +++ b/ja/codes/python/chapter_stack_and_queue/array_deque.py @@ -0,0 +1,129 @@ +""" +File: array_deque.py +Created Time: 2023-03-01 +Author: krahets (krahets@163.com) +""" + + +class ArrayDeque: + """循環配列ベースの双端キュークラス""" + + def __init__(self, capacity: int): + """コンストラクタ""" + self._nums: list[int] = [0] * capacity + self._front: int = 0 + 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 が配列の先頭を超えた場合、末尾に戻る + return (i + self.capacity()) % self.capacity() + + def push_first(self, num: int): + """前端エンキュー""" + if self._size == self.capacity(): + print("双端キューが満杯です") + return + # フロントポインタを左に1つ移動 + # モジュロ演算によってフロントが配列の先頭を超えて末尾に戻ることを実装 + self._front = self.index(self._front - 1) + # num を前端に追加 + self._nums[self._front] = num + self._size += 1 + + def push_last(self, num: int): + """後端エンキュー""" + if self._size == self.capacity(): + print("双端キューが満杯です") + return + # リアポインタを計算、リアインデックス + 1 を指す + rear = self.index(self._front + self._size) + # num を後端に追加 + self._nums[rear] = num + self._size += 1 + + def pop_first(self) -> int: + """前端デキュー""" + num = self.peek_first() + # フロントポインタを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") + return self._nums[self._front] + + def peek_last(self) -> int: + """後端要素にアクセス""" + if self.is_empty(): + raise IndexError("Double-ended queue is empty") + # 後端要素のインデックスを計算 + 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)]) + return res + + +"""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()) + + # 要素にアクセス + peek_first: int = deque.peek_first() + print("前端要素 peek_first =", peek_first) + peek_last: int = deque.peek_last() + print("後端要素 peek_last =", peek_last) + + # 要素をエンキュー + deque.push_last(4) + print("要素 4 を後端エンキュー、deque =", deque.to_array()) + deque.push_first(1) + print("要素 1 を前端エンキュー、deque =", deque.to_array()) + + # 要素をデキュー + pop_last: int = deque.pop_last() + print("後端でデキューされた要素 =", pop_last, "、後端デキュー後の deque =", deque.to_array()) + pop_first: int = deque.pop_first() + print("前端でデキューされた要素 =", pop_first, "、前端デキュー後の deque =", deque.to_array()) + + # 双端キューの長さを取得 + size: int = deque.size() + print("双端キューの長さ size =", size) + + # 双端キューが空かどうかを判定 + is_empty: bool = deque.is_empty() + print("双端キューが空かどうか =", is_empty) \ No newline at end of file diff --git a/ja/codes/python/chapter_stack_and_queue/array_queue.py b/ja/codes/python/chapter_stack_and_queue/array_queue.py new file mode 100644 index 000000000..dc61d4122 --- /dev/null +++ b/ja/codes/python/chapter_stack_and_queue/array_queue.py @@ -0,0 +1,98 @@ +""" +File: array_queue.py +Created Time: 2022-12-01 +Author: Peng Chen (pengchzn@gmail.com) +""" + + +class ArrayQueue: + """循環配列ベースのキュークラス""" + + def __init__(self, size: int): + """コンストラクタ""" + self._nums: list[int] = [0] * size # キュー要素を格納する配列 + self._front: int = 0 # フロントポインタ、フロント要素を指す + 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 push(self, num: int): + """エンキュー""" + if self._size == self.capacity(): + raise IndexError("Queue is full") + # リアポインタを計算、リアインデックス + 1 を指す + # モジュロ演算を使用してリアポインタを配列の末尾から先頭に戻す + rear: int = (self._front + self._size) % self.capacity() + # num をリアに追加 + self._nums[rear] = num + self._size += 1 + + def pop(self) -> int: + """デキュー""" + num: int = self.peek() + # フロントポインタを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") + 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()): + res[i] = self._nums[(j % self.capacity())] + j += 1 + return res + + +"""Driver Code""" +if __name__ == "__main__": + # キューを初期化 + queue = ArrayQueue(10) + + # 要素をエンキュー + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + print("キュー queue =", queue.to_list()) + + # フロント要素にアクセス + peek: int = queue.peek() + print("フロント要素 peek =", peek) + + # 要素をデキュー + pop: int = queue.pop() + print("デキューされた要素 pop =", pop) + print("デキュー後のキュー =", queue.to_list()) + + # キューの長さを取得 + size: int = queue.size() + print("キューの長さ size =", size) + + # キューが空かどうかを判定 + 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 diff --git a/ja/codes/python/chapter_stack_and_queue/array_stack.py b/ja/codes/python/chapter_stack_and_queue/array_stack.py new file mode 100644 index 000000000..9d5e5936f --- /dev/null +++ b/ja/codes/python/chapter_stack_and_queue/array_stack.py @@ -0,0 +1,72 @@ +""" +File: array_stack.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + + +class ArrayStack: + """配列ベースのスタッククラス""" + + def __init__(self): + """コンストラクタ""" + self._stack: list[int] = [] + + def size(self) -> int: + """スタックの長さを取得""" + return len(self._stack) + + def is_empty(self) -> bool: + """スタックが空かどうかを判定""" + return self.size() == 0 + + def push(self, item: int): + """プッシュ""" + self._stack.append(item) + + def pop(self) -> int: + """ポップ""" + if self.is_empty(): + raise IndexError("Stack is empty") + return self._stack.pop() + + def peek(self) -> int: + """スタックトップ要素にアクセス""" + if self.is_empty(): + raise IndexError("Stack is empty") + return self._stack[-1] + + def to_list(self) -> list[int]: + """出力用の配列を返す""" + return self._stack + + +"""Driver Code""" +if __name__ == "__main__": + # スタックを初期化 + stack = ArrayStack() + + # 要素をプッシュ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + 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()) + + # スタックの長さを取得 + size: int = stack.size() + print("スタックの長さ size =", size) + + # 空かどうかを判定 + is_empty: bool = stack.is_empty() + print("スタックが空かどうか =", is_empty) \ No newline at end of file diff --git a/ja/codes/python/chapter_stack_and_queue/deque.py b/ja/codes/python/chapter_stack_and_queue/deque.py new file mode 100644 index 000000000..d72a1df1c --- /dev/null +++ b/ja/codes/python/chapter_stack_and_queue/deque.py @@ -0,0 +1,42 @@ +""" +File: deque.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + +from collections import deque + +"""Driver Code""" +if __name__ == "__main__": + # 双端キューを初期化 + deq: deque[int] = deque() + + # 要素をエンキュー + deq.append(2) # 後端に追加 + deq.append(5) + deq.append(4) + deq.appendleft(3) # 前端に追加 + deq.appendleft(1) + print("双端キュー deque =", deq) + + # 要素にアクセス + 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) + + # 双端キューの長さを取得 + size: int = len(deq) + print("双端キューの長さ size =", size) + + # 双端キューが空かどうかを判定 + is_empty: bool = len(deq) == 0 + print("双端キューが空かどうか =", is_empty) \ No newline at end of file diff --git a/ja/codes/python/chapter_stack_and_queue/linkedlist_deque.py b/ja/codes/python/chapter_stack_and_queue/linkedlist_deque.py new file mode 100644 index 000000000..f4ea6b967 --- /dev/null +++ b/ja/codes/python/chapter_stack_and_queue/linkedlist_deque.py @@ -0,0 +1,151 @@ +""" +File: linkedlist_deque.py +Created Time: 2023-03-01 +Author: krahets (krahets@163.com) +""" + + +class ListNode: + """双方向連結リストノード""" + + def __init__(self, val: int): + """コンストラクタ""" + self.val: int = val + self.next: ListNode | None = None # 後続ノードへの参照 + self.prev: ListNode | None = None # 前駆ノードへの参照 + + +class LinkedListDeque: + """双方向連結リストベースの双端キュークラス""" + + def __init__(self): + """コンストラクタ""" + 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 に向ける + if self.is_empty(): + self._front = self._rear = node + # 前端エンキュー操作 + elif is_front: + # ノードをリストの先頭に追加 + self._front.prev = node + node.next = self._front + self._front = node # ヘッドノードを更新 + # 後端エンキュー操作 + else: + # ノードをリストの末尾に追加 + self._rear.next = node + node.prev = self._rear + 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") + # 前端デキュー操作 + if is_front: + 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 # ヘッドノードを更新 + # 後端デキュー操作 + else: + 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._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") + return self._front.val + + def peek_last(self) -> int: + """後端要素にアクセス""" + if self.is_empty(): + raise IndexError("Double-ended queue is empty") + return self._rear.val + + def to_array(self) -> list[int]: + """出力用の配列を返す""" + node = self._front + res = [0] * self.size() + for i in range(self.size()): + res[i] = node.val + node = node.next + return res + + +"""Driver Code""" +if __name__ == "__main__": + # 双端キューを初期化 + deque = LinkedListDeque() + deque.push_last(3) + deque.push_last(2) + deque.push_last(5) + print("双端キュー deque =", deque.to_array()) + + # 要素にアクセス + peek_first: int = deque.peek_first() + print("前端要素 peek_first =", peek_first) + peek_last: int = deque.peek_last() + print("後端要素 peek_last =", peek_last) + + # 要素をエンキュー + deque.push_last(4) + print("要素 4 を後端エンキュー、deque =", deque.to_array()) + deque.push_first(1) + print("要素 1 を前端エンキュー、deque =", deque.to_array()) + + # 要素をデキュー + pop_last: int = deque.pop_last() + print("後端でデキューされた要素 =", pop_last, "、後端デキュー後の deque =", deque.to_array()) + pop_first: int = deque.pop_first() + print("前端でデキューされた要素 =", pop_first, "、前端デキュー後の deque =", deque.to_array()) + + # 双端キューの長さを取得 + size: int = deque.size() + print("双端キューの長さ size =", size) + + # 双端キューが空かどうかを判定 + is_empty: bool = deque.is_empty() + print("双端キューが空かどうか =", is_empty) \ No newline at end of file diff --git a/ja/codes/python/chapter_stack_and_queue/linkedlist_queue.py b/ja/codes/python/chapter_stack_and_queue/linkedlist_queue.py new file mode 100644 index 000000000..b4132240a --- /dev/null +++ b/ja/codes/python/chapter_stack_and_queue/linkedlist_queue.py @@ -0,0 +1,97 @@ +""" +File: linkedlist_queue.py +Created Time: 2022-12-01 +Author: Peng Chen (pengchzn@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode + + +class LinkedListQueue: + """連結リストベースのキュークラス""" + + def __init__(self): + """コンストラクタ""" + 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): + """エンキュー""" + # テールノードの後ろに num を追加 + node = ListNode(num) + # キューが空の場合、ヘッドとテールノードの両方をそのノードに向ける + if self._front is None: + self._front = node + self._rear = node + # キューが空でない場合、そのノードをテールノードの後ろに追加 + else: + self._rear.next = node + self._rear = node + self._size += 1 + + 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") + return self._front.val + + def to_list(self) -> list[int]: + """出力用のリストに変換""" + queue = [] + temp = self._front + while temp: + queue.append(temp.val) + temp = temp.next + return queue + + +"""Driver Code""" +if __name__ == "__main__": + # キューを初期化 + queue = LinkedListQueue() + + # 要素をエンキュー + queue.push(1) + queue.push(3) + queue.push(2) + queue.push(5) + queue.push(4) + print("キュー queue =", queue.to_list()) + + # フロント要素にアクセス + peek: int = queue.peek() + print("フロント要素 front =", peek) + + # 要素をデキュー + pop_front: int = queue.pop() + print("デキューされた要素 pop =", pop_front) + print("デキュー後のキュー =", queue.to_list()) + + # キューの長さを取得 + size: int = queue.size() + print("キューの長さ size =", size) + + # キューが空かどうかを判定 + is_empty: bool = queue.is_empty() + print("キューが空かどうか =", is_empty) \ No newline at end of file diff --git a/ja/codes/python/chapter_stack_and_queue/linkedlist_stack.py b/ja/codes/python/chapter_stack_and_queue/linkedlist_stack.py new file mode 100644 index 000000000..dd5a54ba0 --- /dev/null +++ b/ja/codes/python/chapter_stack_and_queue/linkedlist_stack.py @@ -0,0 +1,89 @@ +""" +File: linkedlist_stack.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import ListNode + + +class LinkedListStack: + """連結リストベースのスタッククラス""" + + def __init__(self): + """コンストラクタ""" + self._peek: ListNode | None = None + self._size: int = 0 + + def size(self) -> int: + """スタックの長さを取得""" + return self._size + + def is_empty(self) -> bool: + """スタックが空かどうかを判定""" + return self._size == 0 + + def push(self, val: int): + """プッシュ""" + node = ListNode(val) + node.next = self._peek + self._peek = node + self._size += 1 + + def pop(self) -> int: + """ポップ""" + num = self.peek() + self._peek = self._peek.next + self._size -= 1 + return num + + def peek(self) -> int: + """スタックトップ要素にアクセス""" + if self.is_empty(): + raise IndexError("Stack is empty") + return self._peek.val + + def to_list(self) -> list[int]: + """出力用のリストに変換""" + arr = [] + node = self._peek + while node: + arr.append(node.val) + node = node.next + arr.reverse() + return arr + + +"""Driver Code""" +if __name__ == "__main__": + # スタックを初期化 + stack = LinkedListStack() + + # 要素をプッシュ + stack.push(1) + stack.push(3) + stack.push(2) + stack.push(5) + 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()) + + # スタックの長さを取得 + size: int = stack.size() + print("スタックの長さ size =", size) + + # 空かどうかを判定 + is_empty: bool = stack.is_empty() + print("スタックが空かどうか =", is_empty) \ No newline at end of file diff --git a/ja/codes/python/chapter_stack_and_queue/queue.py b/ja/codes/python/chapter_stack_and_queue/queue.py new file mode 100644 index 000000000..267c0cb61 --- /dev/null +++ b/ja/codes/python/chapter_stack_and_queue/queue.py @@ -0,0 +1,39 @@ +""" +File: queue.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + +from collections import deque + +"""Driver Code""" +if __name__ == "__main__": + # キューを初期化 + # Pythonでは、一般的にdequeクラスをキューとして考えます + # queue.Queue()は純粋なキュークラスですが、あまりユーザーフレンドリーではありません + que: deque[int] = deque() + + # 要素をエンキュー + que.append(1) + que.append(3) + que.append(2) + que.append(5) + que.append(4) + print("キュー que =", que) + + # フロント要素にアクセス + front: int = que[0] + print("フロント要素 front =", front) + + # 要素をデキュー + pop: int = que.popleft() + print("デキューされた要素 pop =", pop) + print("デキュー後のキュー =", que) + + # キューの長さを取得 + size: int = len(que) + print("キューの長さ size =", size) + + # キューが空かどうかを判定 + is_empty: bool = len(que) == 0 + print("キューが空かどうか =", is_empty) \ No newline at end of file diff --git a/ja/codes/python/chapter_stack_and_queue/stack.py b/ja/codes/python/chapter_stack_and_queue/stack.py new file mode 100644 index 000000000..80e659331 --- /dev/null +++ b/ja/codes/python/chapter_stack_and_queue/stack.py @@ -0,0 +1,36 @@ +""" +File: stack.py +Created Time: 2022-11-29 +Author: Peng Chen (pengchzn@gmail.com) +""" + +"""Driver Code""" +if __name__ == "__main__": + # スタックを初期化 + # Pythonには組み込みのスタッククラスはありませんが、リストをスタックとして使用できます + stack: list[int] = [] + + # 要素をプッシュ + stack.append(1) + stack.append(3) + stack.append(2) + stack.append(5) + stack.append(4) + print("スタック stack =", stack) + + # スタックトップ要素にアクセス + peek: int = stack[-1] + print("スタックトップ要素 peek =", peek) + + # 要素をポップ + pop: int = stack.pop() + print("ポップされた要素 pop =", pop) + print("ポップ後のスタック =", stack) + + # スタックの長さを取得 + size: int = len(stack) + print("スタックの長さ size =", size) + + # 空かどうかを判定 + is_empty: bool = len(stack) == 0 + print("スタックが空かどうか =", is_empty) \ No newline at end of file diff --git a/ja/codes/python/chapter_tree/array_binary_tree.py b/ja/codes/python/chapter_tree/array_binary_tree.py new file mode 100644 index 000000000..2e42b6df0 --- /dev/null +++ b/ja/codes/python/chapter_tree/array_binary_tree.py @@ -0,0 +1,119 @@ +""" +File: array_binary_tree.py +Created Time: 2023-07-19 +Author: krahets (krahets@163.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +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を返し、空席を表す + if i < 0 or i >= self.size(): + return None + return self._tree[i] + + def left(self, i: int) -> int | None: + """インデックスiのノードの左の子のインデックスを取得""" + return 2 * i + 1 + + def right(self, i: int) -> int | None: + """インデックスiのノードの右の子のインデックスを取得""" + return 2 * i + 2 + + def parent(self, i: int) -> int | None: + """インデックス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) + # 中順走査 + if order == "in": + self.res.append(self.val(i)) + self.dfs(self.right(i), order) + # 後順走査 + if order == "post": + self.res.append(self.val(i)) + + def pre_order(self) -> list[int]: + """前順走査""" + self.res = [] + self.dfs(0, order="pre") + return self.res + + def in_order(self) -> list[int]: + """中順走査""" + self.res = [] + self.dfs(0, order="in") + return self.res + + def post_order(self) -> list[int]: + """後順走査""" + self.res = [] + self.dfs(0, order="post") + return self.res + + +"""ドライバコード""" +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(arr) + 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"その親ノードのインデックスは {p}、値は {abt.val(p)}") + + # 木を走査 + res = abt.level_order() + print("\nレベル順走査:", res) + res = abt.pre_order() + print("前順走査:", res) + res = abt.in_order() + print("中順走査:", res) + res = abt.post_order() + print("後順走査:", res) \ No newline at end of file diff --git a/ja/codes/python/chapter_tree/avl_tree.py b/ja/codes/python/chapter_tree/avl_tree.py new file mode 100644 index 000000000..f6c62844f --- /dev/null +++ b/ja/codes/python/chapter_tree/avl_tree.py @@ -0,0 +1,200 @@ +""" +File: avl_tree.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree + + +class AVLTree: + """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 + if node is not None: + return node.height + return -1 + + def update_height(self, node: TreeNode | None): + """ノードの高さを更新""" + # ノードの高さ = 最も高い部分木の高さ + 1 + node.height = max([self.height(node.left), self.height(node.right)]) + 1 + + def balance_factor(self, node: TreeNode | None) -> int: + """バランス因子を取得""" + # 空ノードのバランス因子は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.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.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のバランス因子を取得 + balance_factor = self.balance_factor(node) + # 左偏り木 + if balance_factor > 1: + if self.balance_factor(node.left) >= 0: + # 右回転 + return self.right_rotate(node) + else: + # 左回転してから右回転 + node.left = self.left_rotate(node.left) + return self.right_rotate(node) + # 右偏り木 + elif balance_factor < -1: + if self.balance_factor(node.right) <= 0: + # 左回転 + return self.left_rotate(node) + else: + # 右回転してから左回転 + node.right = self.right_rotate(node.right) + return self.left_rotate(node) + # バランスの取れた木、回転不要、戻る + return node + + def insert(self, val): + """ノードを挿入""" + 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. 挿入位置を見つけてノードを挿入 + 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. 回転操作を実行して部分木のバランスを復元 + return self.rotate(node) + + def remove(self, val: int): + """ノードを削除""" + 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. ノードを見つけて削除 + if val < node.val: + node.left = self.remove_helper(node.left, val) + elif val > node.val: + node.right = self.remove_helper(node.right, val) + else: + if node.left is None or node.right is None: + child = node.left or node.right + # 子ノード数 = 0、ノードを削除して戻る + if child is None: + return None + # 子ノード数 = 1、ノードを削除 + else: + node = child + else: + # 子ノード数 = 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. 回転操作を実行して部分木のバランスを復元 + return self.rotate(node) + + def search(self, val: int) -> TreeNode | None: + """ノードを探索""" + cur = self._root + # ループで探索、葉ノードを通過した後にブレーク + while cur is not None: + # ターゲットノードはcurの右部分木にある + if cur.val < val: + cur = cur.right + # ターゲットノードはcurの左部分木にある + elif cur.val > val: + cur = cur.left + # ターゲットノードを発見、ループをブレーク + else: + break + # ターゲットノードを返す + return cur + + +"""ドライバコード""" +if __name__ == "__main__": + + def test_insert(tree: AVLTree, val: int): + tree.insert(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_tree(tree.get_root()) + + # 空のAVL木を初期化 + avl_tree = AVLTree() + + # ノードを挿入 + # 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のノードを削除 + + result_node = avl_tree.search(7) + print("\n発見されたノードオブジェクト: {}、ノードの値 = {}".format(result_node, result_node.val)) \ No newline at end of file diff --git a/ja/codes/python/chapter_tree/binary_search_tree.py b/ja/codes/python/chapter_tree/binary_search_tree.py new file mode 100644 index 000000000..0a754c17b --- /dev/null +++ b/ja/codes/python/chapter_tree/binary_search_tree.py @@ -0,0 +1,146 @@ +""" +File: binary_search_tree.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree + + +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の右部分木にある + if cur.val < num: + cur = cur.right + # ターゲットノードは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の右部分木にある + if cur.val < num: + cur = cur.right + # 挿入位置はcurの左部分木にある + else: + cur = cur.left + # ノードを挿入 + node = TreeNode(num) + if pre.val < num: + pre.right = node + else: + pre.left = node + + 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の右部分木にある + if cur.val < num: + cur = cur.right + # 削除するノードはcurの左部分木にある + else: + cur = cur.left + # 削除するノードが存在しない場合、戻る + if cur is None: + return + + # 子ノード数 = 0 または 1 + if cur.left is None or cur.right is None: + # 子ノード数 = 0/1の場合、child = null/その子ノード + child = cur.left or cur.right + # ノードcurを削除 + if cur != self._root: + if pre.left == cur: + pre.left = child + else: + pre.right = child + else: + # 削除されるノードがルートの場合、ルートを再割り当て + self._root = child + # 子ノード数 = 2 + else: + # curの中順走査の次のノードを取得 + tmp: TreeNode = cur.right + while tmp.left is not None: + tmp = tmp.left + # 再帰的にノードtmpを削除 + self.remove(tmp.val) + # curをtmpで置き換え + cur.val = tmp.val + + +"""ドライバコード""" +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_tree(bst.get_root()) + + # ノードを探索 + node = bst.search(7) + print("\n発見されたノードオブジェクト: {}, ノードの値 = {}".format(node, node.val)) + + # ノードを挿入 + bst.insert(16) + print("\nノード16を挿入後の二分木は\n") + print_tree(bst.get_root()) + + # ノードを削除 + bst.remove(1) + print("\nノード1を削除後の二分木は\n") + print_tree(bst.get_root()) + + bst.remove(2) + 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 diff --git a/ja/codes/python/chapter_tree/binary_tree.py b/ja/codes/python/chapter_tree/binary_tree.py new file mode 100644 index 000000000..fc8eee183 --- /dev/null +++ b/ja/codes/python/chapter_tree/binary_tree.py @@ -0,0 +1,41 @@ +""" +File: binary_tree.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, print_tree + + +"""ドライバコード""" +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 + n2.right = n5 + print("\n二分木を初期化\n") + print_tree(n1) + + # ノードの挿入と削除 + P = TreeNode(0) + # ノードPを n1 -> n2 の間に挿入 + n1.left = P + P.left = n2 + print("\nノードPを挿入後\n") + print_tree(n1) + # ノードを削除 + n1.left = n2 + print("\nノードPを削除後\n") + print_tree(n1) \ No newline at end of file diff --git a/ja/codes/python/chapter_tree/binary_tree_bfs.py b/ja/codes/python/chapter_tree/binary_tree_bfs.py new file mode 100644 index 000000000..a329eccb3 --- /dev/null +++ b/ja/codes/python/chapter_tree/binary_tree_bfs.py @@ -0,0 +1,42 @@ +""" +File: binary_tree_bfs.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +from modules import TreeNode, list_to_tree, print_tree +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) # ノードの値を保存 + if node.left is not None: + queue.append(node.left) # 左の子ノードをエンキュー + if node.right is not None: + queue.append(node.right) # 右の子ノードをエンキュー + return res + + +"""ドライバコード""" +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 diff --git a/ja/codes/python/chapter_tree/binary_tree_dfs.py b/ja/codes/python/chapter_tree/binary_tree_dfs.py new file mode 100644 index 000000000..6a96335d7 --- /dev/null +++ b/ja/codes/python/chapter_tree/binary_tree_dfs.py @@ -0,0 +1,65 @@ +""" +File: binary_tree_dfs.py +Created Time: 2022-12-20 +Author: a16su (lpluls001@gmail.com) +""" + +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) +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) + + +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) + + +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) + + +"""ドライバコード""" +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) + + # 中順走査 + res.clear() + in_order(root) + print("\n中順走査のノードシーケンスを出力 = ", res) + + # 後順走査 + res.clear() + post_order(root) + print("\n後順走査のノードシーケンスを出力 = ", res) \ No newline at end of file diff --git a/ja/codes/python/modules/__init__.py b/ja/codes/python/modules/__init__.py new file mode 100644 index 000000000..0f1ca57c3 --- /dev/null +++ b/ja/codes/python/modules/__init__.py @@ -0,0 +1,19 @@ +# PEP 585に従う - 標準コレクションでの型ヒント +# https://peps.python.org/pep-0585/ +from __future__ import annotations + +# 共通ライブラリをここでインポートして、`from module import *`でコードを簡潔にする +from .list_node import ( + ListNode, + list_to_linked_list, + linked_list_to_list, +) +from .tree_node import TreeNode, list_to_tree, tree_to_list +from .vertex import Vertex, vals_to_vets, vets_to_vals +from .print_util import ( + print_matrix, + print_linked_list, + 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 new file mode 100644 index 000000000..b9be878c7 --- /dev/null +++ b/ja/codes/python/modules/list_node.py @@ -0,0 +1,32 @@ +""" +File: list_node.py +Created Time: 2021-12-11 +Author: krahets (krahets@163.com) +""" + + +class ListNode: + """連結リストのノードクラス""" + + def __init__(self, val: int): + 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) + head.next = node + head = head.next + return dum.next + + +def linked_list_to_list(head: ListNode | None) -> list[int]: + """連結リストをリストにシリアライズ""" + arr: list[int] = [] + while head: + arr.append(head.val) + head = head.next + return arr \ No newline at end of file diff --git a/ja/codes/python/modules/print_util.py b/ja/codes/python/modules/print_util.py new file mode 100644 index 000000000..07bf34d66 --- /dev/null +++ b/ja/codes/python/modules/print_util.py @@ -0,0 +1,81 @@ +""" +File: print_util.py +Created Time: 2021-12-11 +Author: krahets (krahets@163.com), msk397 (machangxinq@gmail.com) +""" + +from .tree_node import TreeNode, list_to_tree +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)) + print("[\n" + ",\n".join(s) + "\n]") + + +def print_linked_list(head: ListNode | None): + """連結リストを出力""" + arr: list[int] = linked_list_to_list(head) + print(" -> ".join([str(a) for a in arr])) + + +class Trunk: + def __init__(self, prev, string: str | None = None): + self.prev = prev + self.str = string + + +def show_trunks(p: Trunk | None): + if p is None: + return + show_trunks(p.prev) + print(p.str, end="") + + +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/ + """ + if root is None: + return + + prev_str = " " + trunk = Trunk(prev, prev_str) + print_tree(root.right, trunk, True) + + if prev is None: + trunk.str = "———" + elif is_right: + trunk.str = "/———" + prev_str = " |" + else: + trunk.str = "\———" + prev.str = prev_str + + show_trunks(trunk) + print(" " + str(root.val)) + if prev: + prev.str = prev_str + trunk.str = " |" + print_tree(root.left, trunk, False) + + +def print_dict(hmap: dict): + """辞書を出力""" + for key, value in hmap.items(): + print(key, "->", value) + + +def print_heap(heap: list[int]): + """ヒープを出力""" + print("ヒープの配列表現:", heap) + print("ヒープの木表現:") + root: TreeNode | None = list_to_tree(heap) + print_tree(root) \ No newline at end of file diff --git a/ja/codes/python/modules/tree_node.py b/ja/codes/python/modules/tree_node.py new file mode 100644 index 000000000..7462ab957 --- /dev/null +++ b/ja/codes/python/modules/tree_node.py @@ -0,0 +1,69 @@ +""" +File: tree_node.py +Created Time: 2021-12-11 +Author: krahets (krahets@163.com) +""" + +from collections import deque + + +class TreeNode: + """二分木のノードクラス""" + + def __init__(self, val: int = 0): + self.val: int = val # ノードの値 + self.height: int = 0 # ノードの高さ + 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 + # ——— 1 + # \——— 2 + # | /——— 9 + # \——— 4 + # \——— 8 + + +def list_to_tree_dfs(arr: list[int], i: int) -> TreeNode | 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): + res += [None] * (i - len(res) + 1) + res[i] = root.val + tree_to_list_dfs(root.left, 2 * i + 1, res) + tree_to_list_dfs(root.right, 2 * i + 2, res) + + +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 diff --git a/ja/codes/python/modules/vertex.py b/ja/codes/python/modules/vertex.py new file mode 100644 index 000000000..d052b0e6d --- /dev/null +++ b/ja/codes/python/modules/vertex.py @@ -0,0 +1,20 @@ +# File: vertex.py +# Created Time: 2023-02-23 +# Author: krahets (krahets@163.com) + + +class Vertex: + """頂点クラス""" + + def __init__(self, val: int): + self.val = val + + +def vals_to_vets(vals: list[int]) -> list["Vertex"]: + """値のリスト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 diff --git a/ja/codes/python/test_all.py b/ja/codes/python/test_all.py new file mode 100644 index 000000000..6fa7360a1 --- /dev/null +++ b/ja/codes/python/test_all.py @@ -0,0 +1,33 @@ +import os +import glob +import subprocess + +env = os.environ.copy() +env["PYTHONIOENCODING"] = "utf-8" + +if __name__ == "__main__": + # ソースコードファイルを検索 + src_paths = sorted(glob.glob("ja/codes/python/chapter_*/*.py")) + errors = [] + + # python コードを実行 + for src_path in src_paths: + process = subprocess.Popen( + ["python", src_path], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + env=env, + encoding='utf-8' + ) + # プロセスの完了を待ち、出力とエラーメッセージを取得 + stdout, stderr = process.communicate() + # 終了ステータスをチェック + exit_status = process.returncode + if exit_status != 0: + errors.append(stderr) + + print(f"{len(src_paths)} ファイルをテストしました") + print(f"{len(errors)} ファイルで例外が見つかりました") + if len(errors) > 0: + raise RuntimeError("\n\n".join(errors)) \ No newline at end of file diff --git a/ja/docs/assets/covers/chapter_appendix.jpg b/ja/docs/assets/covers/chapter_appendix.jpg new file mode 100644 index 0000000000000000000000000000000000000000..00a849b573910d145de095c3403f32edce354c82 GIT binary patch literal 116562 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�W2vwO!v3jI|h$%GyI9- zFiF|MP5TM7wPWR6dv;e{=N8wSAF^_S`&PkC8W9w(iBw@cgE@dO&ad!8ot2;I{4B;nZD?(d4KXiuH| zB6TcuTLOmZH%}V)Q{BdOLLfEs9%d1lySL~ucI?}

3#8H~~p!mHVL(ILf6(G9OHk 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

    jg*AbW%|))s71T`(37ciq<`k)R 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 CZj)XB literal 0 HcmV?d00001 diff --git a/ja/docs/assets/covers/chapter_backtracking.jpg b/ja/docs/assets/covers/chapter_backtracking.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1c91c7818f65bb1deb7b7329832b86d0d5ad3923 GIT binary patch literal 129893 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

    LVLMO2jNcHC@;#?h9QI;Fc$mnBBNblf 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 QJ{;#K|5r|r_jmUH0J{rL^Z)<= literal 0 HcmV?d00001 diff --git a/ja/docs/assets/covers/chapter_complexity_analysis.jpg b/ja/docs/assets/covers/chapter_complexity_analysis.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2a19ed360006bc68c7af6628b6541188977be106 GIT binary patch literal 97602 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 Hd>{YWjZAYg literal 0 HcmV?d00001 diff --git a/ja/docs/assets/covers/chapter_data_structure.jpg b/ja/docs/assets/covers/chapter_data_structure.jpg new file mode 100644 index 0000000000000000000000000000000000000000..590a6edaa0a362adc62777893e11fc5d33e9538c GIT binary patch literal 145130 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